From 17d9f5e2aaab602736d96e30f59ba86014565bed Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Sun, 4 Apr 2021 15:24:57 +0200 Subject: [PATCH 001/154] creality.ini: improve bridging --- resources/profiles/Creality.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 1c11210dee0..4f926746f45 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -281,6 +281,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ # Common print preset [print:*common*] avoid_crossing_perimeters = 0 +bridge_acceleration = 250 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 25 @@ -288,6 +289,7 @@ brim_width = 0 clip_multipart_objects = 1 compatible_printers = complete_objects = 0 +default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0.1 ensure_vertical_shell_thickness = 1 @@ -383,6 +385,7 @@ layer_height = 0.08 perimeters = 3 bottom_solid_layers = 9 top_solid_layers = 11 +bridge_flow_ratio = 0.70 [print:*0.10mm*] inherits = *common* @@ -390,6 +393,7 @@ layer_height = 0.10 perimeters = 3 bottom_solid_layers = 7 top_solid_layers = 9 +bridge_flow_ratio = 0.70 [print:*0.12mm*] inherits = *common* @@ -397,12 +401,14 @@ layer_height = 0.12 perimeters = 3 bottom_solid_layers = 6 top_solid_layers = 7 +bridge_flow_ratio = 0.70 [print:*0.16mm*] inherits = *common* layer_height = 0.16 bottom_solid_layers = 5 top_solid_layers = 7 +bridge_flow_ratio = 0.85 [print:*0.20mm*] inherits = *common* From dee6b11167bfd5a371611817c833487b3a965496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 5 Apr 2021 16:47:00 +0200 Subject: [PATCH 002/154] A few test cases for Voronoi diagrams. A few test cases collected from multi-material segmentation. All new test cases are suppressed not to fail a building process. --- tests/libslic3r/test_voronoi.cpp | 237 +++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index bbcea7301fd..f05edfc2fbb 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -7,6 +7,7 @@ #include #include +#include #include @@ -1922,3 +1923,239 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]") REQUIRE(! skeleton_edges.empty()); } + +// Simple detection with complexity N^2 if there is any point in the input polygons that doesn't have Voronoi vertex. +[[maybe_unused]] static bool has_missing_voronoi_vertices(const Polygons &polygons, const VD &vd) +{ + auto are_equal = [](const VD::vertex_type v, const Point &p) { return (Vec2d(v.x(), v.y()) - p.cast()).norm() <= SCALED_EPSILON; }; + + Points poly_points = to_points(polygons); + std::vector found_vertices(poly_points.size()); + for (const Point &point : poly_points) + for (const auto &vertex : vd.vertices()) + if (are_equal(vertex, point)) { + found_vertices[&point - &poly_points.front()] = true; + break; + } + + return std::find(found_vertices.begin(), found_vertices.end(), false) != found_vertices.end(); +} + +// This case is composed of one square polygon, and one of the edges is divided into two parts by a point that lies on this edge. +// In some applications, this point is unnecessary and can be removed (merge two parts to one edge). But for the case of +// multi-material segmentation, these points are necessary. In this case, Voronoi vertex for the point, which divides the edge +// into two parts. Even we add more points to the edge, and then for these points, the Voronoin vertex is also missing. An +// infinity-edge passes through the missing Voronoi vertex. Therefore, this missing Voronoi vertex and edge can be reconstructed +// using the intersection between the infinity-edge with the input polygon. +// Rotation of the polygon solves this problem. +TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]") +{ + REQUIRE(false); + Polygon poly = { + { 25000000, 25000000}, + {-25000000, 25000000}, + {-25000000, -25000000}, + {-12412500, -25000000}, +// {- 1650000, -25000000}, + { 25000000, -25000000} + }; + +// poly.rotate(PI / 6); + + REQUIRE(poly.area() > 0.); + REQUIRE(intersecting_edges({poly}).empty()); + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines); +#endif + +// REQUIRE(!has_missing_voronoi_vertices({poly}, vd)); +} + +// This case is composed of two square polygons (contour and hole), and again one of the edges is divided into two parts by a +// point that lies on this edge, and for this point is Voronoi vertex missing. A difference between the previous and this case is +// that for this case, through the missing Voronoi vertex is passing a finite edge between two internal Voronoin vertices. +// Therefore, this missing Voronoi vertex and edge can be reconstructed using the intersection between the finite edge with the +// input polygon. +// Rotation of the polygons solves this problem. +TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]") +{ + Polygons poly = { + Polygon { + { 50000000, 50000000}, + {-50000000, 50000000}, + {-50000000, -50000000}, + { 50000000, -50000000}, + }, + Polygon { + {-45000000, -45000000}, + {-45000000, 45000000}, + { 45000000, 45000000}, + { 45000000, 8280000}, + { 45000000, -45000000}, + } + }; + +// polygons_rotate(poly, PI / 6); + + double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly) { return a + poly.area(); }); + REQUIRE(area > 0.); + REQUIRE(intersecting_edges(poly).empty()); + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines); +#endif + +// REQUIRE(!has_missing_voronoi_vertices(poly, vd)); +} + +// This case is composed of two polygons, and again one of the edges is divided into two parts by a point that lies on this edge, +// and for this point is Voronoi vertex missing. A difference between the previous cases and this case through the missing +// Voronoi vertex is passing finite edge between one inner Voronoi vertex and one outer Voronoi vertex. +// Rotating the polygon also help solve this problem. +TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]") +{ + Polygons poly = { + Polygon { + {-29715088, -29310899}, + {-29022573, -28618384}, + {-27771147, -27366958}, + {-28539221, -26519393}, + {-30619013, -28586348}, + {-29812018, -29407830}, + }, + Polygon { + {-27035112, -28071875}, + {-27367482, -27770679}, + {-28387008, -28790205}, + {-29309438, -29712635}, + {-29406319, -29809515}, + {-29032985, -30179156}, + } + }; + double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly){ return a + poly.area(); }); + REQUIRE(area > 0.); + REQUIRE(intersecting_edges(poly).empty()); + + // polygons_rotate(poly, PI/180); + // polygons_rotate(poly, PI/6); + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines); +#endif + +// REQUIRE(!has_missing_voronoi_vertices(poly, vd)); +} + +// In this case, the Voronoi vertex (146873, -146873) is included twice. +// Also, near to those duplicate Voronoi vertices is another Voronoi vertex (146872, -146872). +// Rotating the polygon will help solve this problem, but then there arise three very close Voronoi vertices. +// Rotating of the input polygon will help solve this problem. +TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]") +{ + Polygon poly = { + { 25000000, 25000000}, + {-25000000, 25000000}, + {-25000000, -25000000}, + { 146872, -25000000}, + { 9912498, -25000000}, + { 25000000, -25000000}, + { 25000000, - 8056252}, + { 25000000, - 146873}, + { 25000000, 10790627}, + }; + +// poly.rotate(PI / 6); + + REQUIRE(poly.area() > 0.); + REQUIRE(intersecting_edges({poly}).empty()); + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines); +#endif + + [[maybe_unused]] auto has_duplicate_vertices = [](const VD &vd) -> bool { + std::vector vertices; + for (const auto &vertex : vd.vertices()) + vertices.emplace_back(Vec2d(vertex.x(), vertex.y())); + + std::sort(vertices.begin(), vertices.end(), [](const Vec2d &l, const Vec2d &r) { return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); }); + return std::unique(vertices.begin(), vertices.end()) != vertices.end(); + }; + +// REQUIRE(!has_duplicate_vertices(vd)); +} + +// In this case, there are three very close Voronoi vertices like in the previous test case after rotation. There is also one +// missing Voronoi vertex. One infinity-edge (after clip [(146872, -70146871), (146872, -146871)]) passes through this missing +// Voronoi vertex. This infinite edge [(146872, -70146871), (146872, -146871)] and edge [(146873, -146873), (0, 0)] are intersecting. +// They intersect probably because the three points are very close to each other, with a combination of the missing Voronoi vertex. +// Rotating of the input polygon will help solve this problem. +TEST_CASE("Intersecting Voronoi edges", "[Voronoi]") +{ + Polygon poly = { + { 25000000, 25000000}, + {-25000000, 25000000}, + {-25000000, -25000000}, + { 146872, -25000000}, + { 25000000, -25000000}, + { 25000000, - 146873}, + }; + +// poly.rotate(PI / 6); + + REQUIRE(poly.area() > 0.); + REQUIRE(intersecting_edges({poly}).empty()); + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines); +#endif + + [[maybe_unused]] auto has_intersecting_edges = [](const Polygon &poly, const VD &vd) -> bool { + BoundingBox bbox = get_extents(poly); + const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); + + std::vector segments; + for (const Line &line : to_lines(poly)) + segments.emplace_back(Voronoi::Internal::point_type(double(line.a.x()), double(line.a.y())), + Voronoi::Internal::point_type(double(line.b.x()), double(line.b.y()))); + + Lines edges; + for (const auto &edge : vd.edges()) + if (edge.cell()->source_index() < edge.twin()->cell()->source_index()) { + if (edge.is_finite()) { + edges.emplace_back(Point(coord_t(edge.vertex0()->x()), coord_t(edge.vertex0()->y())), + Point(coord_t(edge.vertex1()->x()), coord_t(edge.vertex1()->y()))); + } else if (edge.is_infinite()) { + std::vector samples; + Voronoi::Internal::clip_infinite_edge(poly.points, segments, edge, bbox_dim_max, &samples); + if (!samples.empty()) + edges.emplace_back(Point(coord_t(samples[0].x()), coord_t(samples[0].y())), Point(coord_t(samples[1].x()), coord_t(samples[1].y()))); + } + } + + Point intersect_point; + for (auto first_it = edges.begin(); first_it != edges.end(); ++first_it) + for (auto second_it = first_it + 1; second_it != edges.end(); ++second_it) + if (first_it->intersection(*second_it, &intersect_point) && first_it->a != intersect_point && first_it->b != intersect_point) + return true; + return false; + }; + +// REQUIRE(!has_intersecting_edges(poly, vd)); +} From c04cc3f4095694016e1800fe0346452df6ae72da Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Mon, 5 Apr 2021 17:24:01 +0200 Subject: [PATCH 003/154] creality.ini: add Tough PLAs --- resources/profiles/Creality.ini | 80 +++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 4f926746f45..6d977ade19d 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -21,7 +21,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3BLTOUCH] name = Creality Ender-3 BLTouch @@ -30,7 +30,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3V2] name = Creality Ender-3 V2 @@ -39,7 +39,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3MAX] name = Creality Ender-3 Max @@ -48,7 +48,7 @@ technology = FFF family = ENDER bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER4] name = Creality Ender-4 @@ -57,7 +57,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5] name = Creality Ender-5 @@ -66,7 +66,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5PLUS] name = Creality Ender-5 Plus @@ -75,7 +75,7 @@ technology = FFF family = ENDER bed_model = ender5plus_bed.stl bed_texture = ender5plus.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER6] name = Creality Ender-6 @@ -84,7 +84,7 @@ technology = FFF family = ENDER bed_model = ender6_bed.stl bed_texture = ender6.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER2] name = Creality Ender-2 @@ -93,7 +93,7 @@ technology = FFF family = ENDER bed_model = ender2_bed.stl bed_texture = ender2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR5PRO] name = Creality CR-5 Pro @@ -102,7 +102,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR5PROH] name = Creality CR-5 Pro H @@ -111,7 +111,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR6SE] name = Creality CR-6 SE @@ -120,7 +120,7 @@ technology = FFF family = CR bed_model = cr6se_bed.stl bed_texture = cr6se.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR6MAX] name = Creality CR-6 Max @@ -129,7 +129,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MINI] name = Creality CR-10 Mini @@ -138,7 +138,7 @@ technology = FFF family = CR bed_model = cr10mini_bed.stl bed_texture = cr10mini.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MAX] name = Creality CR-10 Max @@ -147,7 +147,7 @@ technology = FFF family = CR bed_model = cr10max_bed.stl bed_texture = cr10max.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10] name = Creality CR-10 @@ -156,7 +156,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V2] name = Creality CR-10 V2 @@ -165,7 +165,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V3] name = Creality CR-10 V3 @@ -174,7 +174,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S] name = Creality CR-10 S @@ -183,7 +183,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPRO] name = Creality CR-10 S Pro @@ -192,7 +192,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPROV2] name = Creality CR-10 S Pro V2 @@ -201,7 +201,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S4] name = Creality CR-10 S4 @@ -210,7 +210,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S5] name = Creality CR-10 S5 @@ -219,7 +219,7 @@ technology = FFF family = CR bed_model = cr10s5_bed.stl bed_texture = cr10s5.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20] name = Creality CR-20 @@ -228,7 +228,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20PRO] name = Creality CR-20 Pro @@ -237,7 +237,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR200B] name = Creality CR-200B @@ -246,7 +246,7 @@ technology = FFF family = CR bed_model = cr200b_bed.stl bed_texture = cr200b.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR8] name = Creality CR-8 @@ -255,7 +255,7 @@ technology = FFF family = CR bed_model = cr8_bed.stl bed_texture = cr8.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRX] #name = Creality CR-X @@ -264,7 +264,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRXPRO] #name = Creality CR-X Pro @@ -273,7 +273,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -706,6 +706,28 @@ filament_density = 1.24 filament_colour = #125467 filament_spool_weight = 238 +[filament:3DJAKE ecoPLA Tough @CREALITY] +inherits = *PLA* +filament_vendor = 3DJAKE +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 29.99 +filament_density = 1.21 +filament_colour = #125467 + +[filament:FormFutura Tough PLA @CREALITY] +inherits = *PLA* +filament_vendor = FormFutura +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 46.65 +filament_density = 1.21 +filament_colour = #ed000e + [filament:123-3D Jupiter PLA @CREALITY] inherits = *PLA* filament_vendor = 123-3D From 7045fe95550732591289c229a1acfc55a2c62822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 5 Apr 2021 20:32:41 +0200 Subject: [PATCH 004/154] Fix of 7bd412a2cabaf1068b21dee87ce37b67dcd3a912 --- tests/libslic3r/test_voronoi.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index f05edfc2fbb..c78849c01e5 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -1950,7 +1950,6 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]") // Rotation of the polygon solves this problem. TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]") { - REQUIRE(false); Polygon poly = { { 25000000, 25000000}, {-25000000, 25000000}, From 99d8db0255d213f739d736f52299bc25dd061887 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Mar 2021 13:23:24 +0100 Subject: [PATCH 005/154] Wipe tower: don't do sparse infill when there is a soluble filament above it --- src/libslic3r/GCode/WipeTower.cpp | 51 ++++++++++++++++++++++++------- src/libslic3r/GCode/WipeTower.hpp | 1 + 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index f3ecd2cf33a..8385375bb86 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -556,6 +556,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar.push_back(FilamentParameters()); m_filpar[idx].material = config.filament_type.get_at(idx); + m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx); m_filpar[idx].temperature = config.temperature.get_at(idx); m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx); @@ -1192,19 +1193,47 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() const float right = fill_box.ru.x() - 2 * m_perimeter_width; if (dy > m_perimeter_width) { - // Extrude an inverse U at the left of the region. - writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) - .extrude(fill_box.lu + Vec2f(m_perimeter_width * 2, 0.f), 2900 * speed_factor); + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)); - const int n = 1+int((right-left)/m_bridging); - const float dx = (right-left)/n; - for (int i=1;i<=n;++i) { - float x=left+dx*i; - writer.travel(x,writer.y()); - writer.extrude(x,i%2 ? fill_box.rd.y() : fill_box.ru.y()); + // Is there a soluble filament wiped/rammed at the next layer? + // If so, the infill should not be sparse. + bool solid_infill = m_layer_info+1 == m_plan.end() + ? false + : std::any_of((m_layer_info+1)->tool_changes.begin(), + (m_layer_info+1)->tool_changes.end(), + [this](const WipeTowerInfo::ToolChange& tch) { + return m_filpar[tch.new_tool].is_soluble + || m_filpar[tch.old_tool].is_soluble; + }); + + if (solid_infill) { + const float sparse_factor = 1.5f; // 1=solid, 2=every other line, etc. + float y = fill_box.ld.y() + m_perimeter_width; + int n = dy / (m_perimeter_width * sparse_factor); + float spacing = (dy-m_perimeter_width)/(n-1); + int i = 0; + for (i=0; i Date: Wed, 17 Mar 2021 17:42:07 +0100 Subject: [PATCH 006/154] Wipe tower: remove unfinished square wipe tower option --- src/libslic3r/GCode/WipeTower.cpp | 65 +++++-------------------------- src/libslic3r/GCode/WipeTower.hpp | 1 - src/libslic3r/Print.cpp | 4 +- 3 files changed, 10 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 8385375bb86..35e4e7091f4 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -9,17 +9,6 @@ #include "BoundingBox.hpp" -// Experimental "Peter's wipe tower" feature was partially implemented, inspired by -// PJR's idea of alternating two perpendicular wiping directions on a square tower. -// It is probably never going to be finished, there are multiple remaining issues -// and there is probably no need to go down this way. m_peters_wipe_tower variable -// turns this on, maybe it should just be removed. Anyway, the issues are -// - layer's are not exactly square -// - variable width for higher levels -// - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) -// - enable enhanced first layer adhesion - - namespace Slic3r { @@ -738,7 +727,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) - .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) + .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) .append(";--------------------\n" "; CP TOOLCHANGE START\n") .comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based @@ -773,15 +762,11 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) if (last_change_in_layer) {// draw perimeter line writer.set_y_shift(m_y_shift); - if (m_peters_wipe_tower) - writer.rectangle(Vec2f::Zero(), m_layer_info->depth + 3*m_perimeter_width, m_wipe_tower_depth); - else { - writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); - if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle - writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); + writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); - } } } @@ -1141,7 +1126,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) - .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f)) + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)) .append(";--------------------\n" "; CP EMPTY GRID START\n") .comment_with_value(" layer #", m_num_layer_changes + 1); @@ -1174,7 +1159,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() if (m_is_first_layer && m_adhesion) { // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. box.expand(-m_perimeter_width/2.f); - int nsteps = int(floor((box.lu.y() - box.ld.y()) / (2*m_perimeter_width))); + int nsteps = int(std::floor((box.lu.y() - box.ld.y()) / (2*m_perimeter_width))); float step = (box.lu.y() - box.ld.y()) / nsteps; writer.travel(box.ld - Vec2f(m_perimeter_width/2.f, m_perimeter_width/2.f)); if (nsteps >= 0) @@ -1354,9 +1339,6 @@ void WipeTower::generate(std::vector> & plan_tower(); } - if (m_peters_wipe_tower) - make_wipe_tower_square(); - m_layer_info = m_plan.begin(); // we don't know which extruder to start with - we'll set it according to the first toolchange @@ -1376,12 +1358,9 @@ void WipeTower::generate(std::vector> & for (auto layer : m_plan) { set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z); - if (m_peters_wipe_tower) - m_internal_rotation += 90.f; - else - m_internal_rotation += 180.f; + m_internal_rotation += 180.f; - if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) + if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; for (const auto &toolchange : layer.tool_changes) @@ -1410,30 +1389,4 @@ void WipeTower::generate(std::vector> & } } -void WipeTower::make_wipe_tower_square() -{ - const float width = m_wipe_tower_width - 3 * m_perimeter_width; - const float depth = m_wipe_tower_depth - m_perimeter_width; - // area that we actually print into is width*depth - float side = sqrt(depth * width); - - m_wipe_tower_width = side + 3 * m_perimeter_width; - m_wipe_tower_depth = side + 2 * m_perimeter_width; - // For all layers, find how depth changed and update all toolchange depths - for (auto &lay : m_plan) - { - side = sqrt(lay.depth * width); - float width_ratio = width / side; - - //lay.extra_spacing = width_ratio; - for (auto &tch : lay.tool_changes) - tch.required_depth *= width_ratio; - } - - plan_tower(); // propagates depth downwards again (width has changed) - for (auto& lay : m_plan) // depths set, now the spacing - lay.extra_spacing = lay.depth / lay.toolchanges_depth(); -} - - } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 5110a49cfe4..4661f5f5ca4 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -209,7 +209,6 @@ private: SHAPE_REVERSED = -1 }; - const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust const float WT_EPSILON = 1e-3f; float filament_area() const { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e39fd8685e9..b7018976498 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1987,9 +1987,7 @@ void Print::_make_wipe_tower() // Set the extruder & material properties at the wipe tower object. for (size_t i = 0; i < number_of_extruders; ++ i) - - wipe_tower.set_extruder( - i, m_config); + wipe_tower.set_extruder(i, m_config); m_wipe_tower_data.priming = Slic3r::make_unique>( wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); From cda523f8d98b057a65970fb09ae174701fef79ad Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Mar 2021 14:09:19 +0100 Subject: [PATCH 007/154] Wipe tower: slightly changed finish_layer logic so it can be called after any toolchange --- src/libslic3r/GCode/WipeTower.cpp | 33 ++++++++++++++++--------------- src/libslic3r/GCode/WipeTower.hpp | 7 ++++++- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 35e4e7091f4..c155a96a586 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -717,10 +717,10 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) // Otherwise we are going to Unload only. And m_layer_info would be invalid. } - box_coordinates cleaning_box( + box_coordinates cleaning_box( Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), m_wipe_tower_width - m_perimeter_width, - (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5f*m_perimeter_width + (tool != (unsigned int)(-1) ? wipe_area+m_depth_traversed-0.5f*m_perimeter_width : m_wipe_tower_depth-m_perimeter_width)); WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); @@ -760,7 +760,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) m_depth_traversed += wipe_area; - if (last_change_in_layer) {// draw perimeter line + /*if (last_change_in_layer) {// draw perimeter line writer.set_y_shift(m_y_shift); writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle @@ -768,7 +768,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) .add_wipe_point(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); } - } + }*/ if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. @@ -1116,9 +1116,8 @@ void WipeTower::toolchange_Wipe( WipeTower::ToolChangeResult WipeTower::finish_layer() { - // This should only be called if the layer is not finished yet. - // Otherwise the caller would likely travel to the wipe tower in vain. assert(! this->layer_finished()); + m_current_layer_finished = true; size_t old_tool = m_current_tool; @@ -1134,8 +1133,8 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); - box_coordinates fill_box(Vec2f(m_perimeter_width, m_depth_traversed + m_perimeter_width), - m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); + box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), + m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel @@ -1143,14 +1142,15 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; box_coordinates box = fill_box; - for (int i=0;i<2;++i) { - if (! toolchanges_on_layer) { - if (i==0) box.expand(m_perimeter_width); - else box.expand(-m_perimeter_width); - } - else i=2; // only draw the inner perimeter, outer has been already drawn by tool_change(...) + + // inner perimeter of the sparse section, if there is space for it: + if (fill_box.lu.y() - fill_box.ld.y() > m_perimeter_width) writer.rectangle(box.ld, box.rd.x()-box.ld.x(), box.ru.y()-box.rd.y(), 2900*speed_factor); - } + + // outer perimeter (always): + writer.rectangle(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), + m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + // we are in one of the corners, travel to ld along the perimeter: if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); @@ -1311,7 +1311,8 @@ void WipeTower::save_on_last_wipe() tool_change(toolchange.new_tool); float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into - float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f); + //float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f); + float length_to_save = finish_layer().total_extrusion_length_in_plane(); float length_to_wipe = volume_to_length(m_layer_info->tool_changes.back().wipe_volume, m_perimeter_width,m_layer_info->height) - m_layer_info->tool_changes.back().first_wipe_line - length_to_save; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 4661f5f5ca4..e423aa3982c 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -132,6 +132,7 @@ public: m_is_first_layer = is_first_layer; m_print_brim = is_first_layer; m_depth_traversed = 0.f; + m_current_layer_finished = false; m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; if (is_first_layer) { this->m_num_layer_changes = 0; @@ -175,7 +176,10 @@ public: // Is the current layer finished? bool layer_finished() const { - return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); + return m_current_layer_finished;/*( (m_is_first_layer + ? m_wipe_tower_depth - m_perimeter_width + : m_layer_info->depth + ) < m_depth_traversed + WT_EPSILON);*/ } std::vector get_used_filament() const { return m_used_filament_length; } @@ -268,6 +272,7 @@ private: const std::vector> wipe_volumes; float m_depth_traversed = 0.f; // Current y position at the wipe tower. + bool m_current_layer_finished = false; bool m_left_to_right = true; float m_extra_spacing = 1.f; From 6719d91430c7ea0136fba9c334b54d9649582437 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 19 Mar 2021 15:26:55 +0100 Subject: [PATCH 008/154] Wipe tower: don't use soluble filament for perimeters, sparse infill or first layer --- src/libslic3r/GCode/WipeTower.cpp | 122 ++++++++++++++++++++---------- src/libslic3r/GCode/WipeTower.hpp | 5 ++ 2 files changed, 87 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index c155a96a586..c5c7c0e308c 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1125,10 +1125,8 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) - .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)) - .append(";--------------------\n" - "; CP EMPTY GRID START\n") - .comment_with_value(" layer #", m_num_layer_changes + 1); + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; @@ -1141,23 +1139,22 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; - box_coordinates box = fill_box; // inner perimeter of the sparse section, if there is space for it: - if (fill_box.lu.y() - fill_box.ld.y() > m_perimeter_width) - writer.rectangle(box.ld, box.rd.x()-box.ld.x(), box.ru.y()-box.rd.y(), 2900*speed_factor); + if (fill_box.ru.y() - fill_box.rd.y() > m_perimeter_width - WT_EPSILON) + writer.rectangle(fill_box.ld, fill_box.rd.x()-fill_box.ld.x(), fill_box.ru.y()-fill_box.rd.y(), 2900*speed_factor); // outer perimeter (always): writer.rectangle(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); - // we are in one of the corners, travel to ld along the perimeter: if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); if (writer.y() > fill_box.ld.y()+EPSILON) writer.travel(writer.x(),fill_box.ld.y()); if (m_is_first_layer && m_adhesion) { // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. + box_coordinates box = fill_box; box.expand(-m_perimeter_width/2.f); int nsteps = int(std::floor((box.lu.y() - box.ld.y()) / (2*m_perimeter_width))); float step = (box.lu.y() - box.ld.y()) / nsteps; @@ -1178,7 +1175,10 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() const float right = fill_box.ru.x() - 2 * m_perimeter_width; if (dy > m_perimeter_width) { - writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)); + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); // Is there a soluble filament wiped/rammed at the next layer? // If so, the infill should not be sparse. @@ -1219,16 +1219,15 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.add_wipe_point(Vec2f(writer.x(), writer.y())) .add_wipe_point(Vec2f(left, writer.y())); } + + writer.append("; CP EMPTY GRID END\n" + ";------------------\n\n\n\n\n\n\n"); } else { writer.add_wipe_point(Vec2f(writer.x(), writer.y())) .add_wipe_point(Vec2f(right, writer.y())); } } - writer.append("; CP EMPTY GRID END\n" - ";------------------\n\n\n\n\n\n\n"); - - m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; // Ask our writer about how much material was consumed. @@ -1307,23 +1306,60 @@ void WipeTower::save_on_last_wipe() if (m_layer_info->tool_changes.size()==0) // we have no way to save anything on an empty layer continue; - for (const auto &toolchange : m_layer_info->tool_changes) + // Which toolchange will finish_layer extrusions be subtracted from? + int idx = first_toolchange_to_nonsoluble(m_layer_info->tool_changes); + + for (int i=0; itool_changes.size()); ++i) { + auto& toolchange = m_layer_info->tool_changes[i]; tool_change(toolchange.new_tool); - float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into - //float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f); - float length_to_save = finish_layer().total_extrusion_length_in_plane(); - float length_to_wipe = volume_to_length(m_layer_info->tool_changes.back().wipe_volume, - m_perimeter_width,m_layer_info->height) - m_layer_info->tool_changes.back().first_wipe_line - length_to_save; + if (i == idx) { + float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into + float length_to_save = finish_layer().total_extrusion_length_in_plane(); + float length_to_wipe = volume_to_length(toolchange.wipe_volume, + m_perimeter_width, m_layer_info->height) - toolchange.first_wipe_line - length_to_save; - length_to_wipe = std::max(length_to_wipe,0.f); - float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing; + length_to_wipe = std::max(length_to_wipe,0.f); + float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing; - //depth += (int(length_to_extrude / width) + 1) * m_perimeter_width; - m_layer_info->tool_changes.back().required_depth = m_layer_info->tool_changes.back().ramming_depth + depth_to_wipe; + toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe; + } + } } } + +// Return index of first toolchange that switches to non-soluble extruder +// ot -1 if there is no such toolchange. +int WipeTower::first_toolchange_to_nonsoluble( + const std::vector& tool_changes) const +{ + for (size_t idx=0; idx> &result) @@ -1364,25 +1400,31 @@ void WipeTower::generate(std::vector> & if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; - for (const auto &toolchange : layer.tool_changes) - layer_result.emplace_back(tool_change(toolchange.new_tool)); + int idx = first_toolchange_to_nonsoluble(layer.tool_changes); + ToolChangeResult finish_layer_tcr; - if (! layer_finished()) { - auto finish_layer_toolchange = finish_layer(); - if ( ! layer.tool_changes.empty() ) { // we will merge it to the last toolchange - auto& last_toolchange = layer_result.back(); - if (last_toolchange.end_pos != finish_layer_toolchange.start_pos) { - char buf[2048]; // Add a travel move from tc1.end_pos to tc2.start_pos. - sprintf(buf, "G1 X%.3f Y%.3f F7200\n", finish_layer_toolchange.start_pos.x(), finish_layer_toolchange.start_pos.y()); - last_toolchange.gcode += buf; - } - last_toolchange.gcode += finish_layer_toolchange.gcode; - last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end()); - last_toolchange.end_pos = finish_layer_toolchange.end_pos; - last_toolchange.wipe_path = finish_layer_toolchange.wipe_path; - } + if (idx == -1) { + // if there is no toolchange switching to non-soluble, finish layer + // will be called at the very beginning. That's the last possibility + // where a nonsoluble tool can be. + finish_layer_tcr = finish_layer(); + } + + for (int i=0; i m_used_filament_length; + // Return index of first toolchange that switches to non-soluble extruder + // ot -1 if there is no such toolchange. + int first_toolchange_to_nonsoluble( + const std::vector& tool_changes) const; + // Returns gcode for wipe tower brim // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower From 5beca060ed8726a58343c3cfc3f2fddbe980d41b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Mar 2021 14:13:53 +0100 Subject: [PATCH 009/154] Wipe tower: refactoring of brim and solid infill on first layer --- src/libslic3r/GCode.cpp | 6 +- src/libslic3r/GCode.hpp | 6 +- src/libslic3r/GCode/WipeTower.cpp | 277 +++++++++++------------------- src/libslic3r/GCode/WipeTower.hpp | 81 ++++----- src/libslic3r/Print.cpp | 4 +- 5 files changed, 145 insertions(+), 229 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d7e72b4adef..0e464ee6d2b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -435,19 +435,18 @@ namespace Slic3r { { std::string gcode; assert(m_layer_idx >= 0); - if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < (int)m_tool_changes.size()) { if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); - // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, // resulting in a wipe tower with sparse layers. double wipe_tower_z = -1; bool ignore_sparse = false; if (gcodegen.config().wipe_tower_no_sparse_layers.value) { wipe_tower_z = m_last_wipe_tower_print_z; - ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); if (m_tool_change_idx == 0 && !ignore_sparse) wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; } @@ -457,7 +456,6 @@ namespace Slic3r { m_last_wipe_tower_print_z = wipe_tower_z; } } - m_brim_done = true; } return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 9ecabd47df6..08ab8300248 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -75,8 +75,8 @@ public: m_tool_changes(tool_changes), m_final_purge(final_purge), m_layer_idx(-1), - m_tool_change_idx(0), - m_brim_done(false) {} + m_tool_change_idx(0) + {} std::string prime(GCode &gcodegen); void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } @@ -105,8 +105,6 @@ private: // Current layer index. int m_layer_idx; int m_tool_change_idx; - bool m_brim_done; - bool i_have_brim = false; double m_last_wipe_tower_print_z = 0.f; }; diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index c5c7c0e308c..8e7f003615f 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -205,7 +205,7 @@ public: WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) { return extrude(dest.x(), dest.y(), f); } - + WipeTowerWriter& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f) { Vec2f corners[4]; @@ -231,6 +231,14 @@ public: return (*this); } + WipeTowerWriter& rectangle(const WipeTower::box_coordinates& box, const float f = 0.f) + { + rectangle(Vec2f(box.ld.x(), box.ld.y()), + box.ru.x() - box.lu.x(), + box.ru.y() - box.rd.y(), f); + return (*this); + } + WipeTowerWriter& load(float e, float f = 0.f) { if (e == 0.f && (f == 0.f || f == m_current_feedrate)) @@ -512,7 +520,6 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector WipeTower::prime( m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) - // so that tool_change() will know to extrude the wipe tower brim: - m_print_brim = true; - return results; } WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) { - if ( m_print_brim ) - return toolchange_Brim(); - size_t old_tool = m_current_tool; + bool first_layer = m_layer_info == m_plan.begin(); - float wipe_area = 0.f; - bool last_change_in_layer = false; + float wipe_area = 0.f; float wipe_volume = 0.f; // Finds this toolchange info @@ -706,9 +707,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) { for (const auto &b : m_layer_info->tool_changes) if ( b.new_tool == tool ) { - wipe_volume = b.wipe_volume; - if (tool == m_layer_info->tool_changes.back().new_tool) - last_change_in_layer = true; + wipe_volume = b.wipe_volume; wipe_area = b.required_depth * m_layer_info->extra_spacing; break; } @@ -749,7 +748,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (tool != (unsigned int)-1){ // This is not the last change. toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, - m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); + first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Load(writer, cleaning_box); writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road @@ -760,16 +759,6 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) m_depth_traversed += wipe_area; - /*if (last_change_in_layer) {// draw perimeter line - writer.set_y_shift(m_y_shift); - writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); - if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle - writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); - - } - }*/ - if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. writer.speed_override_restore(); @@ -787,66 +776,6 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) return construct_tcr(writer, false, old_tool); } -WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_offset) -{ - size_t old_tool = m_current_tool; - - const box_coordinates wipeTower_box( - Vec2f::Zero(), - m_wipe_tower_width, - m_wipe_tower_depth); - - WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); - writer.set_extrusion_flow(m_extrusion_flow * 1.1f) - .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. - .set_initial_tool(m_current_tool) - .append(";-------------------------------------\n" - "; CP WIPE TOWER FIRST LAYER BRIM START\n"); - - Vec2f initial_position = wipeTower_box.lu - Vec2f(m_wipe_tower_brim_width + 2*m_perimeter_width, 0); - writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); - - // Prime the extruder left of the wipe tower. - writer.extrude_explicit(wipeTower_box.ld - Vec2f(m_wipe_tower_brim_width + 2*m_perimeter_width, 0), - 1.5f * m_extrusion_flow * (wipeTower_box.lu.y() - wipeTower_box.ld.y()), 2400); - - // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. - // Extrude brim around the future wipe tower ('normal' spacing with no extra void space). - box_coordinates box(wipeTower_box); - float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4); - - // How many perimeters shall the brim have? - size_t loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing; - - for (size_t i = 0; i < loops_num; ++ i) { - box.expand(spacing); - writer.travel (box.ld, 7000) - .extrude(box.lu, 2100).extrude(box.ru) - .extrude(box.rd ).extrude(box.ld); - } - - // Save actual brim width to be later passed to the Print object, which will use it - // for skirt calculation and pass it to GLCanvas for precise preview box - m_wipe_tower_brim_width_real = wipeTower_box.ld.x() - box.ld.x() + spacing/2.f; - - box.expand(-spacing); - writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(box.ld) - .add_wipe_point(box.rd); - - writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" - ";-----------------------------------\n"); - - m_print_brim = false; // Mark the brim as extruded - - // Ask our writer about how much material was consumed: - if (m_current_tool < m_used_filament_length.size()) - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - - return construct_tcr(writer, false, old_tool); -} - - // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. void WipeTower::toolchange_Unload( @@ -855,6 +784,7 @@ void WipeTower::toolchange_Unload( const std::string& current_material, const int new_temperature) { + bool first_layer = m_layer_info == m_plan.begin(); float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; @@ -943,7 +873,7 @@ void WipeTower::toolchange_Unload( // be already set and there is no need to change anything. Also, the temperature could be changed // for wrong extruder. if (m_semm) { - if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait. + if (new_temperature != 0 && (new_temperature != m_old_temperature || first_layer) ) { // Set the extruder temperature, but don't wait. // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). writer.set_extruder_temp(new_temperature, false); @@ -1049,9 +979,10 @@ void WipeTower::toolchange_Wipe( float wipe_volume) { // Increase flow on first layer, slow down print. - writer.set_extrusion_flow(m_extrusion_flow * (m_is_first_layer ? 1.18f : 1.f)) + bool first_layer = m_layer_info == m_plan.begin(); + writer.set_extrusion_flow(m_extrusion_flow * (first_layer ? 1.18f : 1.f)) .append("; CP TOOLCHANGE WIPE\n"); - float wipe_coeff = m_is_first_layer ? 0.5f : 1.f; + float wipe_coeff = first_layer ? 0.5f : 1.f; const float& xl = cleaning_box.ld.x(); const float& xr = cleaning_box.rd.x(); @@ -1129,7 +1060,8 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() // Slow down on the 1st layer. - float speed_factor = m_is_first_layer ? 0.5f : 1.f; + bool first_layer = m_layer_info == m_plan.begin(); + float speed_factor = first_layer ? 0.5f : 1.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); @@ -1139,96 +1071,103 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; + box_coordinates wipeTower_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), + m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); // inner perimeter of the sparse section, if there is space for it: if (fill_box.ru.y() - fill_box.rd.y() > m_perimeter_width - WT_EPSILON) writer.rectangle(fill_box.ld, fill_box.rd.x()-fill_box.ld.x(), fill_box.ru.y()-fill_box.rd.y(), 2900*speed_factor); - // outer perimeter (always): - writer.rectangle(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), - m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); - // we are in one of the corners, travel to ld along the perimeter: if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); if (writer.y() > fill_box.ld.y()+EPSILON) writer.travel(writer.x(),fill_box.ld.y()); - if (m_is_first_layer && m_adhesion) { - // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. - box_coordinates box = fill_box; - box.expand(-m_perimeter_width/2.f); - int nsteps = int(std::floor((box.lu.y() - box.ld.y()) / (2*m_perimeter_width))); - float step = (box.lu.y() - box.ld.y()) / nsteps; - writer.travel(box.ld - Vec2f(m_perimeter_width/2.f, m_perimeter_width/2.f)); - if (nsteps >= 0) - for (int i = 0; i < nsteps; ++i) { - writer.extrude(box.ld.x()+m_perimeter_width/2.f, writer.y() + 0.5f * step); - writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y()); - writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y() + 0.5f * step); - writer.extrude(box.ld.x() + m_perimeter_width / 2.f, writer.y()); + // Extrude infill to support the material to be printed above. + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + float left = fill_box.lu.x() + 2*m_perimeter_width; + float right = fill_box.ru.x() - 2 * m_perimeter_width; + if (dy > m_perimeter_width) + { + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); + + // Is there a soluble filament wiped/rammed at the next layer? + // If so, the infill should not be sparse. + bool solid_infill = m_layer_info+1 == m_plan.end() + ? false + : std::any_of((m_layer_info+1)->tool_changes.begin(), + (m_layer_info+1)->tool_changes.end(), + [this](const WipeTowerInfo::ToolChange& tch) { + return m_filpar[tch.new_tool].is_soluble + || m_filpar[tch.old_tool].is_soluble; + }); + solid_infill |= first_layer && m_adhesion; + + if (solid_infill) { + float sparse_factor = 1.5f; // 1=solid, 2=every other line, etc. + if (first_layer) { // the infill should touch perimeters + left -= m_perimeter_width; + right += m_perimeter_width; + sparse_factor = 1.f; } - writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(box.rd.x()-m_perimeter_width/2.f,writer.y()); - } - else { // Extrude a sparse infill to support the material to be printed above. - const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); - const float left = fill_box.lu.x() + 2*m_perimeter_width; - const float right = fill_box.ru.x() - 2 * m_perimeter_width; - if (dy > m_perimeter_width) - { - writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) - .append(";--------------------\n" - "; CP EMPTY GRID START\n") - .comment_with_value(" layer #", m_num_layer_changes + 1); - - // Is there a soluble filament wiped/rammed at the next layer? - // If so, the infill should not be sparse. - bool solid_infill = m_layer_info+1 == m_plan.end() - ? false - : std::any_of((m_layer_info+1)->tool_changes.begin(), - (m_layer_info+1)->tool_changes.end(), - [this](const WipeTowerInfo::ToolChange& tch) { - return m_filpar[tch.new_tool].is_soluble - || m_filpar[tch.old_tool].is_soluble; - }); - - if (solid_infill) { - const float sparse_factor = 1.5f; // 1=solid, 2=every other line, etc. - float y = fill_box.ld.y() + m_perimeter_width; - int n = dy / (m_perimeter_width * sparse_factor); - float spacing = (dy-m_perimeter_width)/(n-1); - int i = 0; - for (i=0; i> &result) { if (m_plan.empty()) - return; m_extra_spacing = 1.f; @@ -1394,7 +1328,7 @@ void WipeTower::generate(std::vector> & std::vector layer_result; for (auto layer : m_plan) { - set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z); + set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z); m_internal_rotation += 180.f; if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) @@ -1428,7 +1362,6 @@ void WipeTower::generate(std::vector> & } result.emplace_back(std::move(layer_result)); - m_is_first_layer = false; } } diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 4fec64b3335..cfd3a9e1166 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -84,6 +84,37 @@ public: } }; + struct box_coordinates + { + box_coordinates(float left, float bottom, float width, float height) : + ld(left , bottom ), + lu(left , bottom + height), + rd(left + width, bottom ), + ru(left + width, bottom + height) {} + box_coordinates(const Vec2f &pos, float width, float height) : box_coordinates(pos(0), pos(1), width, height) {} + void translate(const Vec2f &shift) { + ld += shift; lu += shift; + rd += shift; ru += shift; + } + void translate(const float dx, const float dy) { translate(Vec2f(dx, dy)); } + void expand(const float offset) { + ld += Vec2f(- offset, - offset); + lu += Vec2f(- offset, offset); + rd += Vec2f( offset, - offset); + ru += Vec2f( offset, offset); + } + void expand(const float offset_x, const float offset_y) { + ld += Vec2f(- offset_x, - offset_y); + lu += Vec2f(- offset_x, offset_y); + rd += Vec2f( offset_x, - offset_y); + ru += Vec2f( offset_x, offset_y); + } + Vec2f ld; // left down + Vec2f lu; // left upper + Vec2f rd; // right lower + Vec2f ru; // right upper + }; + // Construct ToolChangeResult from current state of WipeTower and WipeTowerWriter. // WipeTowerWriter is moved from ! ToolChangeResult construct_tcr(WipeTowerWriter& writer, @@ -102,7 +133,7 @@ public: // Appends into internal structure m_plan containing info about the future wipe tower // to be used before building begins. The entries must be added ordered in z. - void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f); + void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f); // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" void generate(std::vector> &result); @@ -129,8 +160,6 @@ public: { m_z_pos = print_z; m_layer_height = layer_height; - m_is_first_layer = is_first_layer; - m_print_brim = is_first_layer; m_depth_traversed = 0.f; m_current_layer_finished = false; m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; @@ -176,10 +205,7 @@ public: // Is the current layer finished? bool layer_finished() const { - return m_current_layer_finished;/*( (m_is_first_layer - ? m_wipe_tower_depth - m_perimeter_width - : m_layer_info->depth - ) < m_depth_traversed + WT_EPSILON);*/ + return m_current_layer_finished; } std::vector get_used_filament() const { return m_used_filament_length; } @@ -232,7 +258,6 @@ private: float m_z_pos = 0.f; // Current Z position. float m_layer_height = 0.f; // Current layer height. size_t m_max_color_changes = 0; // Maximum number of color changes per layer. - bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower. int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) // G-code generator parameters. @@ -299,39 +324,7 @@ private: void save_on_last_wipe(); - struct box_coordinates - { - box_coordinates(float left, float bottom, float width, float height) : - ld(left , bottom ), - lu(left , bottom + height), - rd(left + width, bottom ), - ru(left + width, bottom + height) {} - box_coordinates(const Vec2f &pos, float width, float height) : box_coordinates(pos(0), pos(1), width, height) {} - void translate(const Vec2f &shift) { - ld += shift; lu += shift; - rd += shift; ru += shift; - } - void translate(const float dx, const float dy) { translate(Vec2f(dx, dy)); } - void expand(const float offset) { - ld += Vec2f(- offset, - offset); - lu += Vec2f(- offset, offset); - rd += Vec2f( offset, - offset); - ru += Vec2f( offset, offset); - } - void expand(const float offset_x, const float offset_y) { - ld += Vec2f(- offset_x, - offset_y); - lu += Vec2f(- offset_x, offset_y); - rd += Vec2f( offset_x, - offset_y); - ru += Vec2f( offset_x, offset_y); - } - Vec2f ld; // left down - Vec2f lu; // left upper - Vec2f rd; // right lower - Vec2f ru; // right upper - }; - - - // to store information about tool changes for a given layer + // to store information about tool changes for a given layer struct WipeTowerInfo{ struct ToolChange { size_t old_tool; @@ -366,12 +359,6 @@ private: int first_toolchange_to_nonsoluble( const std::vector& tool_changes) const; - - // Returns gcode for wipe tower brim - // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower - // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower - ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); - void toolchange_Unload( WipeTowerWriter &writer, const box_coordinates &cleaning_box, diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b7018976498..975d40208c1 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2013,8 +2013,8 @@ void Print::_make_wipe_tower() volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // request a toolchange at the wipe tower with at least volume_to_wipe purging amount - wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, - first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back(), volume_to_wipe); + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, + current_extruder_id, extruder_id, volume_to_wipe); current_extruder_id = extruder_id; } } From 641040d3f7db8c4065bedf5ab3b4b523eb80263c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Mar 2021 09:23:46 +0100 Subject: [PATCH 010/154] Wipe tower: fix wipe moves after recent changes --- src/libslic3r/GCode/WipeTower.cpp | 47 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 8e7f003615f..b0bc50aeeb0 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1029,17 +1029,16 @@ void WipeTower::toolchange_Wipe( m_left_to_right = !m_left_to_right; } - // this is neither priming nor not the last toolchange on this layer - we are - // going back to the model - wipe the nozzle. - if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) { + // We may be going back to the model - wipe the nozzle. If this is followed + // by finish_layer, this wipe path will be overwritten. + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(writer.x(), writer.y() - dy) + .add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); + + if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) m_left_to_right = !m_left_to_right; - writer.add_wipe_point(writer.x(), writer.y()) - .add_wipe_point(writer.x(), writer.y() - dy) - .add_wipe_point(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); - } - - writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. + writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. } @@ -1071,7 +1070,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; - box_coordinates wipeTower_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), + box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); // inner perimeter of the sparse section, if there is space for it: @@ -1121,9 +1120,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() .extrude(i%2 ? left : right, y); y = y + spacing; } - writer.extrude(writer.x(), fill_box.lu.y()) - .add_wipe_point(Vec2f(writer.x(), writer.y())) - .add_wipe_point(Vec2f(i%2 ? left : right, writer.y())); + writer.extrude(writer.x(), fill_box.lu.y()); } else { // Extrude an inverse U at the left of the region and the sparse infill. writer.extrude(fill_box.lu + Vec2f(m_perimeter_width * 2, 0.f), 2900 * speed_factor); @@ -1135,26 +1132,18 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.travel(x,writer.y()); writer.extrude(x,i%2 ? fill_box.rd.y() : fill_box.ru.y()); } - writer.add_wipe_point(Vec2f(writer.x(), writer.y())) - .add_wipe_point(Vec2f(left, writer.y())); } writer.append("; CP EMPTY GRID END\n" ";------------------\n\n\n\n\n\n\n"); } - else { - writer.add_wipe_point(Vec2f(writer.x(), writer.y())) - .add_wipe_point(Vec2f(right, writer.y())); - } - // TODO: - wipe path je potřeba nastavit níž - // - kouknout na wipe_tower_error.3mf // outer perimeter (always): - writer.rectangle(wipeTower_box); + writer.rectangle(wt_box); // brim (first layer only) if (first_layer) { - box_coordinates box = wipeTower_box; + box_coordinates box = wt_box; float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4); // How many perimeters shall the brim have? size_t loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing; @@ -1166,9 +1155,19 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() // Save actual brim width to be later passed to the Print object, which will use it // for skirt calculation and pass it to GLCanvas for precise preview box - m_wipe_tower_brim_width_real = wipeTower_box.ld.x() - box.ld.x() + spacing/2.f; + m_wipe_tower_brim_width_real = wt_box.ld.x() - box.ld.x() + spacing/2.f; + wt_box = box; } + // Now prepare future wipe. box contains rectangle that was extruded last (ccw). + Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : + (writer.pos() == wt_box.rd ? wt_box.ru : + (writer.pos() == wt_box.ru ? wt_box.lu : + wt_box.ld))); + writer.add_wipe_point(writer.pos()) + .add_wipe_point(target); + + // Ask our writer about how much material was consumed. // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. if (! m_no_sparse_layers || toolchanges_on_layer) From 5717ba2e6c8aecedb17b86af0a333413536eb9ce Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Mar 2021 14:07:28 +0100 Subject: [PATCH 011/154] Wipe tower: set travel feedrate for a move from custom toolchange position to the wipe tower (#5483) --- src/libslic3r/GCode/WipeTower.cpp | 18 ++++++++++++++---- src/libslic3r/GCode/WipeTower.hpp | 4 +++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index b0bc50aeeb0..81b87f71507 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -111,8 +111,10 @@ public: WipeTowerWriter& feedrate(float f) { - if (f != m_current_feedrate) + if (f != m_current_feedrate) { m_gcode += "G1" + set_format_F(f) + "\n"; + m_current_feedrate = f; + } return *this; } @@ -523,9 +525,14 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector WipeTower::prime( if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); writer.speed_override_restore() - .feedrate(6000) + .feedrate(m_travel_speed * 60.f) .flush_planner_queue() .reset_extruder() .append("; CP PRIMING END\n" @@ -762,7 +769,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. writer.speed_override_restore(); - writer.feedrate(6000) + writer.feedrate(m_travel_speed * 60.f) .flush_planner_queue() .reset_extruder() .append("; CP TOOLCHANGE END\n" @@ -933,7 +940,10 @@ void WipeTower::toolchange_Change( // gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the // postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before. Vec2f current_pos = writer.pos_rotated(); - writer.append(std::string("G1 X") + std::to_string(current_pos.x()) + " Y" + std::to_string(current_pos.y()) + never_skip_tag() + "\n"); + writer.feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483 + .append(std::string("G1 X") + std::to_string(current_pos.x()) + + " Y" + std::to_string(current_pos.y()) + + never_skip_tag() + "\n"); // The toolchange Tn command will be inserted later, only in case that the user does // not provide a custom toolchange gcode. diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index cfd3a9e1166..fd9fbe57b2f 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -259,6 +259,8 @@ private: float m_layer_height = 0.f; // Current layer height. size_t m_max_color_changes = 0; // Maximum number of color changes per layer. int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) + float m_travel_speed = 0.f; + float m_first_layer_speed = 0.f; // G-code generator parameters. float m_cooling_tube_retraction = 0.f; @@ -383,6 +385,6 @@ private: -}; // namespace Slic3r +} // namespace Slic3r #endif // WipeTowerPrusaMM_hpp_ From dce95fdeaebbc9a82dde362d4fa60e2c2356dad1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Mar 2021 13:26:05 +0100 Subject: [PATCH 012/154] Wipe tower: respect first_layer_speed --- src/libslic3r/GCode/WipeTower.cpp | 35 ++++++++++++++++++------------- src/libslic3r/Print.cpp | 4 ++-- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 81b87f71507..8b321a3c8de 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -526,12 +526,16 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector cleaning_box.lu.y()-0.5f*m_perimeter_width) break; // in case next line would not fit @@ -1070,7 +1075,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() // Slow down on the 1st layer. bool first_layer = m_layer_info == m_plan.begin(); - float speed_factor = first_layer ? 0.5f : 1.f; + float feedrate = first_layer ? m_first_layer_speed * 60.f : 2900.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); @@ -1085,7 +1090,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() // inner perimeter of the sparse section, if there is space for it: if (fill_box.ru.y() - fill_box.rd.y() > m_perimeter_width - WT_EPSILON) - writer.rectangle(fill_box.ld, fill_box.rd.x()-fill_box.ld.x(), fill_box.ru.y()-fill_box.rd.y(), 2900*speed_factor); + writer.rectangle(fill_box.ld, fill_box.rd.x()-fill_box.ld.x(), fill_box.ru.y()-fill_box.rd.y(), feedrate); // we are in one of the corners, travel to ld along the perimeter: if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); @@ -1126,14 +1131,14 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() float spacing = (dy-m_perimeter_width)/(n-1); int i=0; for (i=0; i Date: Wed, 24 Mar 2021 09:37:39 +0100 Subject: [PATCH 013/154] Wipe tower: reorder extruders so first layer starts with soluble if possible That way it will not be wiped on first layer --- src/libslic3r/GCode/ToolOrdering.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 8520cf35bd9..c45e2601583 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -329,6 +329,16 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id) lt.extruders.front() = last_extruder_id; break; } + + // On first layer with wipe tower, prefer a soluble extruder + // at the beginning, so it is not wiped on the first layer. + if (lt == m_layer_tools[0] && m_print_config_ptr && m_print_config_ptr->wipe_tower) { + for (size_t i = 0; ifilament_soluble.get_at(lt.extruders[i]-1)) { // 1-based... + std::swap(lt.extruders[i], lt.extruders.front()); + break; + } + } } last_extruder_id = lt.extruders.back(); } From f0cd00bef7986f53de81da36e7cc39b45b771351 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 5 Apr 2021 23:37:25 +0200 Subject: [PATCH 014/154] Wipe tower: correctly detect first layer even with 'No sparse layers' option enabled --- src/libslic3r/GCode/WipeTower.cpp | 22 +++++++++++----------- src/libslic3r/GCode/WipeTower.hpp | 3 +++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 8b321a3c8de..8f104eab6db 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -708,7 +708,6 @@ std::vector WipeTower::prime( WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) { size_t old_tool = m_current_tool; - bool first_layer = m_layer_info == m_plan.begin(); float wipe_area = 0.f; float wipe_volume = 0.f; @@ -759,7 +758,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (tool != (unsigned int)-1){ // This is not the last change. toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, - first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); + is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Load(writer, cleaning_box); writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road @@ -795,7 +794,6 @@ void WipeTower::toolchange_Unload( const std::string& current_material, const int new_temperature) { - bool first_layer = m_layer_info == m_plan.begin(); float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; @@ -884,7 +882,7 @@ void WipeTower::toolchange_Unload( // be already set and there is no need to change anything. Also, the temperature could be changed // for wrong extruder. if (m_semm) { - if (new_temperature != 0 && (new_temperature != m_old_temperature || first_layer) ) { // Set the extruder temperature, but don't wait. + if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer()) ) { // Set the extruder temperature, but don't wait. // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). writer.set_extruder_temp(new_temperature, false); @@ -993,8 +991,7 @@ void WipeTower::toolchange_Wipe( float wipe_volume) { // Increase flow on first layer, slow down print. - bool first_layer = m_layer_info == m_plan.begin(); - writer.set_extrusion_flow(m_extrusion_flow * (first_layer ? 1.18f : 1.f)) + writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f)) .append("; CP TOOLCHANGE WIPE\n"); const float& xl = cleaning_box.ld.x(); const float& xr = cleaning_box.rd.x(); @@ -1006,7 +1003,7 @@ void WipeTower::toolchange_Wipe( float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height); float dy = m_extra_spacing*m_perimeter_width; - const float target_speed = first_layer ? m_first_layer_speed * 60.f : 4800.f; + const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : 4800.f; float wipe_speed = 0.33f * target_speed; // if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway) @@ -1074,7 +1071,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() // Slow down on the 1st layer. - bool first_layer = m_layer_info == m_plan.begin(); + bool first_layer = is_first_layer(); float feedrate = first_layer ? m_first_layer_speed * 60.f : 2900.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), @@ -1201,11 +1198,14 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); - if (old_tool==new_tool) // new layer without toolchanges - we are done - return; + if (m_first_layer_idx == size_t(-1) && (! m_no_sparse_layers || old_tool != new_tool)) + m_first_layer_idx = m_plan.size() - 1; + + if (old_tool == new_tool) // new layer without toolchanges - we are done + return; // this is an actual toolchange - let's calculate depth to reserve on the wipe tower - float depth = 0.f; + float depth = 0.f; float width = m_wipe_tower_width - 3*m_perimeter_width; float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f), m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator, diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index fd9fbe57b2f..c3b2770b709 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -261,6 +261,7 @@ private: int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) float m_travel_speed = 0.f; float m_first_layer_speed = 0.f; + size_t m_first_layer_idx = size_t(-1); // G-code generator parameters. float m_cooling_tube_retraction = 0.f; @@ -303,6 +304,8 @@ private: bool m_left_to_right = true; float m_extra_spacing = 1.f; + bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } + // Calculates extrusion flow needed to produce required line width for given layer height float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow { From 25dd2452fa05ca244d6154f567b6796c2215ea8b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 30 Mar 2021 12:46:09 +0200 Subject: [PATCH 015/154] Duplicated Marlin firmware flavor to 'Marlin (legacy)' and 'Marlin Firmware' The two flavors should be identical after this commit, except that GCodeProcessor.cpp was not updated. This shall be done in a later step. --- src/libslic3r/GCode.cpp | 8 +++++--- src/libslic3r/GCode/WipeTower.cpp | 4 ++-- src/libslic3r/GCodeWriter.cpp | 4 +++- src/libslic3r/Print.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 5 ++++- src/libslic3r/PrintConfig.hpp | 3 ++- src/slic3r/GUI/Tab.cpp | 9 ++++++--- 7 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0e464ee6d2b..ab241010492 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -784,7 +784,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re namespace DoExport { static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) { - silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode; + silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin || config.gcode_flavor == gcfMarlinFirmware) + && config.silent_mode; processor.reset(); processor.apply_config(config); processor.enable_stealth_time_estimator(silent_time_estimator_enabled); @@ -1355,7 +1356,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu bbox_prime.offset(0.5f); bool overlap = bbox_prime.overlap(bbox_print); - if (print.config().gcode_flavor == gcfMarlin) { + if (print.config().gcode_flavor == gcfMarlin || print.config().gcode_flavor == gcfMarlinFirmware) { _write(file, this->retract()); _write(file, "M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. if (overlap) { @@ -1558,7 +1559,8 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. void GCode::print_machine_envelope(FILE *file, Print &print) { - if (print.config().gcode_flavor.value == gcfMarlin && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { + if ((print.config().gcode_flavor.value == gcfMarlin || print.config().gcode_flavor.value == gcfMarlinFirmware) + && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", int(print.config().machine_max_acceleration_x.values.front() + 0.5), int(print.config().machine_max_acceleration_y.values.front() + 0.5), diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 8f104eab6db..f59ab341c08 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -342,7 +342,7 @@ public: WipeTowerWriter& speed_override_backup() { // This is only supported by Prusa at this point (https://github.com/prusa3d/PrusaSlicer/issues/3114) - if (m_gcode_flavor == gcfMarlin) + if (m_gcode_flavor == gcfMarlin || m_gcode_flavor == gcfMarlinFirmware) m_gcode += "M220 B\n"; return *this; } @@ -350,7 +350,7 @@ public: // Let the firmware restore the active speed override value. WipeTowerWriter& speed_override_restore() { - if (m_gcode_flavor == gcfMarlin) + if (m_gcode_flavor == gcfMarlin || m_gcode_flavor == gcfMarlinFirmware) m_gcode += "M220 R\n"; return *this; } diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 847398e69fd..233f07b43e2 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -20,7 +20,8 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) this->config.apply(print_config, true); m_extrusion_axis = this->config.get_extrusion_axis(); m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; - m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? + bool is_marlin = print_config.gcode_flavor.value == gcfMarlin || print_config.gcode_flavor.value == gcfMarlinFirmware; + m_max_acceleration = std::lrint((is_marlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? print_config.machine_max_acceleration_extruding.values.front() : 0); } @@ -49,6 +50,7 @@ std::string GCodeWriter::preamble() if (FLAVOR_IS(gcfRepRapSprinter) || FLAVOR_IS(gcfRepRapFirmware) || FLAVOR_IS(gcfMarlin) || + FLAVOR_IS(gcfMarlinFirmware) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || FLAVOR_IS(gcfSmoothie)) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 1801b658e23..2cb2940e691 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1293,7 +1293,7 @@ std::string Print::validate(std::string* warning) const } if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware && - m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) + m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin && m_config.gcode_flavor != gcfMarlinFirmware) return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2c6918acf0f..b795eaf20e9 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1103,6 +1103,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("teacup"); def->enum_values.push_back("makerware"); def->enum_values.push_back("marlin"); + def->enum_values.push_back("marlinfirmware"); def->enum_values.push_back("sailfish"); def->enum_values.push_back("mach3"); def->enum_values.push_back("machinekit"); @@ -1113,7 +1114,8 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("Teacup"); def->enum_labels.push_back("MakerWare (MakerBot)"); - def->enum_labels.push_back("Marlin"); + def->enum_labels.push_back("Marlin (legacy)"); + def->enum_labels.push_back("Marlin Firmware"); def->enum_labels.push_back("Sailfish (MakerBot)"); def->enum_labels.push_back("Mach3/LinuxCNC"); def->enum_labels.push_back("Machinekit"); @@ -3631,6 +3633,7 @@ std::string FullPrintConfig::validate() this->gcode_flavor.value != gcfRepRapSprinter && this->gcode_flavor.value != gcfRepRapFirmware && this->gcode_flavor.value != gcfMarlin && + this->gcode_flavor.value != gcfMarlinFirmware && this->gcode_flavor.value != gcfMachinekit && this->gcode_flavor.value != gcfRepetier) return "--use-firmware-retraction is only supported by Marlin, Smoothie, RepRapFirmware, Repetier and Machinekit firmware"; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 10f3b11d5e8..7f6ccf3c040 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -24,7 +24,7 @@ namespace Slic3r { enum GCodeFlavor : unsigned char { - gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, + gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfMarlinFirmware, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, }; @@ -121,6 +121,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::get keys_map["teacup"] = gcfTeacup; keys_map["makerware"] = gcfMakerWare; keys_map["marlin"] = gcfMarlin; + keys_map["marlinfirmware"] = gcfMarlinFirmware; keys_map["sailfish"] = gcfSailfish; keys_map["smoothie"] = gcfSmoothie; keys_map["mach3"] = gcfMach3; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 11c4875ebd1..1d10f3a8e74 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2582,7 +2582,8 @@ PageShp TabPrinter::build_kinematics_page() void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) { size_t n_before_extruders = 2; // Count of pages before Extruder pages - bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + auto flavor = m_config->option>("gcode_flavor")->value; + bool is_marlin_flavor = (flavor == gcfMarlin || flavor == gcfMarlinFirmware); /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages * and be cause of application crash, when try to change Preset in moment, @@ -2852,7 +2853,8 @@ void TabPrinter::toggle_options() if (m_active_page->title() == "General") { toggle_option("single_extruder_multi_material", have_multiple_extruders); - bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + auto flavor = m_config->option>("gcode_flavor")->value; + bool is_marlin_flavor = flavor == gcfMarlin || flavor == gcfMarlinFirmware; // Disable silent mode for non-marlin firmwares. toggle_option("silent_mode", is_marlin_flavor); } @@ -2920,7 +2922,8 @@ void TabPrinter::toggle_options() } if (m_active_page->title() == "Machine limits" && m_machine_limits_description_line) { - assert(m_config->option>("gcode_flavor")->value == gcfMarlin); + assert(m_config->option>("gcode_flavor")->value == gcfMarlin + || m_config->option>("gcode_flavor")->value == gcfMarlinFirmware); const auto *machine_limits_usage = m_config->option>("machine_limits_usage"); bool enabled = machine_limits_usage->value != MachineLimitsUsage::Ignore; bool silent_mode = m_config->opt_bool("silent_mode"); From 76de47ffca417af333b0b6288b4245acc03881fd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 30 Mar 2021 13:30:18 +0200 Subject: [PATCH 016/154] Renamed the gcfMarlin enum value to gcfMarlinLegacy so we never mistake it for the new one There should be no functional change. --- src/libslic3r/GCode.cpp | 6 +++--- src/libslic3r/GCode/GCodeProcessor.cpp | 18 +++++++++--------- src/libslic3r/GCode/WipeTower.cpp | 4 ++-- src/libslic3r/GCodeWriter.cpp | 4 ++-- src/libslic3r/Print.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 2 +- src/libslic3r/PrintConfig.hpp | 4 ++-- src/slic3r/GUI/Tab.cpp | 6 +++--- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ab241010492..68fbe62d585 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -784,7 +784,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re namespace DoExport { static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) { - silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin || config.gcode_flavor == gcfMarlinFirmware) + silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) && config.silent_mode; processor.reset(); processor.apply_config(config); @@ -1356,7 +1356,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu bbox_prime.offset(0.5f); bool overlap = bbox_prime.overlap(bbox_print); - if (print.config().gcode_flavor == gcfMarlin || print.config().gcode_flavor == gcfMarlinFirmware) { + if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) { _write(file, this->retract()); _write(file, "M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. if (overlap) { @@ -1559,7 +1559,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. void GCode::print_machine_envelope(FILE *file, Print &print) { - if ((print.config().gcode_flavor.value == gcfMarlin || print.config().gcode_flavor.value == gcfMarlinFirmware) + if ((print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", int(print.config().machine_max_acceleration_x.values.front() + 0.5), diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7c89cf467f2..be408c08fc1 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -823,7 +823,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]); } - if (m_flavor == gcfMarlin && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) + if (m_flavor == gcfMarlinLegacy && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) m_time_processor.machine_limits = reinterpret_cast(config); // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. @@ -934,7 +934,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } - if (m_flavor == gcfMarlin) { + if (m_flavor == gcfMarlinLegacy) { const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); if (machine_max_acceleration_x != nullptr) m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; @@ -1646,23 +1646,23 @@ bool GCodeProcessor::process_cura_tags(const std::string_view comment) if (pos != comment.npos) { const std::string_view flavor = comment.substr(pos + tag.length()); if (flavor == "BFB") - m_flavor = gcfMarlin; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // << ??????????????????????? else if (flavor == "Mach3") m_flavor = gcfMach3; else if (flavor == "Makerbot") m_flavor = gcfMakerWare; else if (flavor == "UltiGCode") - m_flavor = gcfMarlin; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // << ??????????????????????? else if (flavor == "Marlin(Volumetric)") - m_flavor = gcfMarlin; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // << ??????????????????????? else if (flavor == "Griffin") - m_flavor = gcfMarlin; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // << ??????????????????????? else if (flavor == "Repetier") m_flavor = gcfRepetier; else if (flavor == "RepRap") m_flavor = gcfRepRapFirmware; else if (flavor == "Marlin") - m_flavor = gcfMarlin; + m_flavor = gcfMarlinLegacy; else BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown flavor: " << flavor; @@ -2575,7 +2575,7 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate // http://smoothieware.org/supported-g-codes - float factor = (m_flavor == gcfMarlin || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; + float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || @@ -2749,7 +2749,7 @@ void GCodeProcessor::process_T(const std::string_view command) int eid = 0; if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) { // Specific to the MMU2 V2 (see https://www.help.prusa3d.com/en/article/prusa-specific-g-codes_112173): - if (m_flavor == gcfMarlin && (command == "Tx" || command == "Tc" || command == "T?")) + if (m_flavor == gcfMarlinLegacy && (command == "Tx" || command == "Tc" || command == "T?")) return; // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677 diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index f59ab341c08..10c68d07665 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -342,7 +342,7 @@ public: WipeTowerWriter& speed_override_backup() { // This is only supported by Prusa at this point (https://github.com/prusa3d/PrusaSlicer/issues/3114) - if (m_gcode_flavor == gcfMarlin || m_gcode_flavor == gcfMarlinFirmware) + if (m_gcode_flavor == gcfMarlinLegacy || m_gcode_flavor == gcfMarlinFirmware) m_gcode += "M220 B\n"; return *this; } @@ -350,7 +350,7 @@ public: // Let the firmware restore the active speed override value. WipeTowerWriter& speed_override_restore() { - if (m_gcode_flavor == gcfMarlin || m_gcode_flavor == gcfMarlinFirmware) + if (m_gcode_flavor == gcfMarlinLegacy || m_gcode_flavor == gcfMarlinFirmware) m_gcode += "M220 R\n"; return *this; } diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 233f07b43e2..5bf0a30daf9 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -20,7 +20,7 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) this->config.apply(print_config, true); m_extrusion_axis = this->config.get_extrusion_axis(); m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; - bool is_marlin = print_config.gcode_flavor.value == gcfMarlin || print_config.gcode_flavor.value == gcfMarlinFirmware; + bool is_marlin = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware; m_max_acceleration = std::lrint((is_marlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? print_config.machine_max_acceleration_extruding.values.front() : 0); } @@ -49,7 +49,7 @@ std::string GCodeWriter::preamble() } if (FLAVOR_IS(gcfRepRapSprinter) || FLAVOR_IS(gcfRepRapFirmware) || - FLAVOR_IS(gcfMarlin) || + FLAVOR_IS(gcfMarlinLegacy) || FLAVOR_IS(gcfMarlinFirmware) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2cb2940e691..ff433a48fd6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1293,7 +1293,7 @@ std::string Print::validate(std::string* warning) const } if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware && - m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin && m_config.gcode_flavor != gcfMarlinFirmware) + m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlinLegacy && m_config.gcode_flavor != gcfMarlinFirmware) return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b795eaf20e9..9c6ac2662c8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3632,7 +3632,7 @@ std::string FullPrintConfig::validate() this->gcode_flavor.value != gcfSmoothie && this->gcode_flavor.value != gcfRepRapSprinter && this->gcode_flavor.value != gcfRepRapFirmware && - this->gcode_flavor.value != gcfMarlin && + this->gcode_flavor.value != gcfMarlinLegacy && this->gcode_flavor.value != gcfMarlinFirmware && this->gcode_flavor.value != gcfMachinekit && this->gcode_flavor.value != gcfRepetier) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 7f6ccf3c040..4f94f8859dd 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -24,7 +24,7 @@ namespace Slic3r { enum GCodeFlavor : unsigned char { - gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfMarlinFirmware, gcfSailfish, gcfMach3, gcfMachinekit, + gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, }; @@ -120,7 +120,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::get keys_map["repetier"] = gcfRepetier; keys_map["teacup"] = gcfTeacup; keys_map["makerware"] = gcfMakerWare; - keys_map["marlin"] = gcfMarlin; + keys_map["marlin"] = gcfMarlinLegacy; keys_map["marlinfirmware"] = gcfMarlinFirmware; keys_map["sailfish"] = gcfSailfish; keys_map["smoothie"] = gcfSmoothie; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1d10f3a8e74..915fa52677f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2583,7 +2583,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) { size_t n_before_extruders = 2; // Count of pages before Extruder pages auto flavor = m_config->option>("gcode_flavor")->value; - bool is_marlin_flavor = (flavor == gcfMarlin || flavor == gcfMarlinFirmware); + bool is_marlin_flavor = (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware); /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages * and be cause of application crash, when try to change Preset in moment, @@ -2854,7 +2854,7 @@ void TabPrinter::toggle_options() toggle_option("single_extruder_multi_material", have_multiple_extruders); auto flavor = m_config->option>("gcode_flavor")->value; - bool is_marlin_flavor = flavor == gcfMarlin || flavor == gcfMarlinFirmware; + bool is_marlin_flavor = flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware; // Disable silent mode for non-marlin firmwares. toggle_option("silent_mode", is_marlin_flavor); } @@ -2922,7 +2922,7 @@ void TabPrinter::toggle_options() } if (m_active_page->title() == "Machine limits" && m_machine_limits_description_line) { - assert(m_config->option>("gcode_flavor")->value == gcfMarlin + assert(m_config->option>("gcode_flavor")->value == gcfMarlinLegacy || m_config->option>("gcode_flavor")->value == gcfMarlinFirmware); const auto *machine_limits_usage = m_config->option>("machine_limits_usage"); bool enabled = machine_limits_usage->value != MachineLimitsUsage::Ignore; From 0e8f871361844453f56f832fd0feb90ff1bd1a07 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 30 Mar 2021 13:37:49 +0200 Subject: [PATCH 017/154] Implemented new acceleration control behaviour for the new Marlin firmware flavor: - show extra travel acceleration settings in 'Machine limits' page in Printer Settings when the new firmware flavor is selected - updated tooltips on the config values (they were basically wrong even in the current version) - 'Marlin (legacy)' firmware flavor behaviour should not change: it exports M204 Pa Rb Ta (where a, b are the values from machine limits) at the beginning of gcode and it uses M204 S... for feature type dependent acceleration settings (legacy variant of M204 P.. T..) - new Marlin Firmware exports M204 Pa Rb Tc (where a,b,c are the values from machine limits). Feature type dependent acceleration is set using M204 P..., not overriding the travel acceleration. --- src/libslic3r/GCode.cpp | 12 +++++++++++- src/libslic3r/GCodeWriter.cpp | 8 ++++++-- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 23 ++++++++++++++++++----- src/libslic3r/PrintConfig.hpp | 7 +++++-- src/slic3r/GUI/Tab.cpp | 15 +++++++++++++++ src/slic3r/GUI/Tab.hpp | 1 + 7 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 68fbe62d585..0e2b1a57f6e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1571,10 +1571,20 @@ void GCode::print_machine_envelope(FILE *file, Print &print) int(print.config().machine_max_feedrate_y.values.front() + 0.5), int(print.config().machine_max_feedrate_z.values.front() + 0.5), int(print.config().machine_max_feedrate_e.values.front() + 0.5)); + + // Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about + // backwards compatibility: https://github.com/prusa3d/PrusaSlicer/issues/1089 + // Legacy Marlin should export travel acceleration the same as printing acceleration. + // MarlinFirmware has the two separated. + int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy + ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) + : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5)); + travel_acc); + + fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", print.config().machine_max_jerk_x.values.front(), print.config().machine_max_jerk_y.values.front(), diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 5bf0a30daf9..bc853311379 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -207,8 +207,12 @@ std::string GCodeWriter::set_acceleration(unsigned int acceleration) // M202: Set max travel acceleration gcode << "M202 X" << acceleration << " Y" << acceleration; } else if (FLAVOR_IS(gcfRepRapFirmware)) { - // M204: Set default acceleration - gcode << "M204 P" << acceleration; + // M204: Set default acceleration + gcode << "M204 P" << acceleration; + } else if (FLAVOR_IS(gcfMarlinFirmware)) { + // This is new MarlinFirmware with separated print/retraction/travel acceleration. + // Use M204 P, we don't want to override travel acc by M204 S (which is deprecated anyway). + gcode << "M204 P" << acceleration; } else { // M204: Set default acceleration gcode << "M204 S" << acceleration; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ecb20a18ecc..d57bcb4516a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -468,7 +468,7 @@ const std::vector& Preset::machine_limits_options() static std::vector s_opts; if (s_opts.empty()) { s_opts = { - "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", + "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel", "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", "machine_min_extruding_rate", "machine_min_travel_rate", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 9c6ac2662c8..fd33f5807ce 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1469,21 +1469,34 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 0., 0. }); - // M204 S... [mm/sec^2] + // M204 P... [mm/sec^2] def = this->add("machine_max_acceleration_extruding", coFloats); def->full_label = L("Maximum acceleration when extruding"); def->category = L("Machine limits"); - def->tooltip = L("Maximum acceleration when extruding (M204 S)"); + def->tooltip = L("Maximum acceleration when extruding (M204 P)\n\n" + "Marlin (legacy) firmware flavor will use this also " + "as travel acceleration (M204 T)."); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); + + + // M204 R... [mm/sec^2] + def = this->add("machine_max_acceleration_retracting", coFloats); + def->full_label = L("Maximum acceleration when retracting"); + def->category = L("Machine limits"); + def->tooltip = L("Maximum acceleration when retracting (M204 R)"); def->sidetext = L("mm/s²"); def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); // M204 T... [mm/sec^2] - def = this->add("machine_max_acceleration_retracting", coFloats); - def->full_label = L("Maximum acceleration when retracting"); + def = this->add("machine_max_acceleration_travel", coFloats); + def->full_label = L("Maximum acceleration for travel moves"); def->category = L("Machine limits"); - def->tooltip = L("Maximum acceleration when retracting (M204 T)"); + def->tooltip = L("Maximum acceleration for travel moves (M204 T)"); def->sidetext = L("mm/s²"); def->min = 0; def->mode = comAdvanced; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 4f94f8859dd..2ee6bc06131 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -726,10 +726,12 @@ public: ConfigOptionFloats machine_max_feedrate_y; ConfigOptionFloats machine_max_feedrate_z; ConfigOptionFloats machine_max_feedrate_e; - // M204 S... [mm/sec^2] + + // M204 P... R... T...[mm/sec^2] ConfigOptionFloats machine_max_acceleration_extruding; - // M204 T... [mm/sec^2] ConfigOptionFloats machine_max_acceleration_retracting; + ConfigOptionFloats machine_max_acceleration_travel; + // M205 X... Y... Z... E... [mm/sec] ConfigOptionFloats machine_max_jerk_x; ConfigOptionFloats machine_max_jerk_y; @@ -754,6 +756,7 @@ protected: OPT_PTR(machine_max_feedrate_e); OPT_PTR(machine_max_acceleration_extruding); OPT_PTR(machine_max_acceleration_retracting); + OPT_PTR(machine_max_acceleration_travel); OPT_PTR(machine_max_jerk_x); OPT_PTR(machine_max_jerk_y); OPT_PTR(machine_max_jerk_z); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 915fa52677f..933ae1a4dbd 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2264,6 +2264,13 @@ void TabPrinter::build_fff() m_use_silent_mode = val; } } + if (opt_key == "gcode_flavor") { + bool supports_travel_acceleration = (boost::any_cast(value) == int(gcfMarlinFirmware)); + if (supports_travel_acceleration != m_supports_travel_acceleration) { + m_rebuild_kinematics_page = true; + m_supports_travel_acceleration = supports_travel_acceleration; + } + } build_unregular_pages(); update_dirty(); on_value_change(opt_key, value); @@ -2560,6 +2567,8 @@ PageShp TabPrinter::build_kinematics_page() } append_option_line(optgroup, "machine_max_acceleration_extruding"); append_option_line(optgroup, "machine_max_acceleration_retracting"); + if (m_supports_travel_acceleration) + append_option_line(optgroup, "machine_max_acceleration_travel"); optgroup = page->new_optgroup(L("Jerk limits")); for (const std::string &axis : axes) { @@ -2954,6 +2963,12 @@ void TabPrinter::update_fff() m_use_silent_mode = m_config->opt_bool("silent_mode"); } + bool supports_travel_acceleration = (m_config->option>("gcode_flavor")->value == gcfMarlinFirmware); + if (m_supports_travel_acceleration != supports_travel_acceleration) { + m_rebuild_kinematics_page = true; + m_supports_travel_acceleration = supports_travel_acceleration; + } + toggle_options(); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 8cbc6585a75..9ccbcda28e2 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -425,6 +425,7 @@ class TabPrinter : public Tab private: bool m_has_single_extruder_MM_page = false; bool m_use_silent_mode = false; + bool m_supports_travel_acceleration = false; void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key); bool m_rebuild_kinematics_page = false; ogStaticText* m_machine_limits_description_line {nullptr}; From 311a5a3e01cd388bf6a5a9ce20d0d673ba04e98a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 31 Mar 2021 12:34:28 +0200 Subject: [PATCH 018/154] GCodeProcessor use new flavor gcfMarlinFirmware --- src/libslic3r/GCode/GCodeProcessor.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index be408c08fc1..0a10ec4cc09 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -823,7 +823,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]); } - if (m_flavor == gcfMarlinLegacy && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) m_time_processor.machine_limits = reinterpret_cast(config); // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. @@ -934,7 +934,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } - if (m_flavor == gcfMarlinLegacy) { + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) { const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); if (machine_max_acceleration_x != nullptr) m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; @@ -991,6 +991,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_max_acceleration_retracting != nullptr) m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; + const ConfigOptionFloats* machine_max_acceleration_travel = config.option("machine_max_acceleration_travel"); + if (machine_max_acceleration_travel != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values; + const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); if (machine_min_extruding_rate != nullptr) m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; @@ -1646,17 +1650,17 @@ bool GCodeProcessor::process_cura_tags(const std::string_view comment) if (pos != comment.npos) { const std::string_view flavor = comment.substr(pos + tag.length()); if (flavor == "BFB") - m_flavor = gcfMarlinLegacy; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // is this correct ? else if (flavor == "Mach3") m_flavor = gcfMach3; else if (flavor == "Makerbot") m_flavor = gcfMakerWare; else if (flavor == "UltiGCode") - m_flavor = gcfMarlinLegacy; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // is this correct ? else if (flavor == "Marlin(Volumetric)") - m_flavor = gcfMarlinLegacy; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // is this correct ? else if (flavor == "Griffin") - m_flavor = gcfMarlinLegacy; // << ??????????????????????? + m_flavor = gcfMarlinLegacy; // is this correct ? else if (flavor == "Repetier") m_flavor = gcfRepetier; else if (flavor == "RepRap") @@ -2575,7 +2579,7 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate // http://smoothieware.org/supported-g-codes - float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; + float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || @@ -2749,7 +2753,7 @@ void GCodeProcessor::process_T(const std::string_view command) int eid = 0; if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) { // Specific to the MMU2 V2 (see https://www.help.prusa3d.com/en/article/prusa-specific-g-codes_112173): - if (m_flavor == gcfMarlinLegacy && (command == "Tx" || command == "Tc" || command == "T?")) + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && (command == "Tx" || command == "Tc" || command == "T?")) return; // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677 From 6deb8338f39f221177fac441ef7b574da62e0cb7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 31 Mar 2021 15:20:11 +0200 Subject: [PATCH 019/154] GCodeProcessor added travel acceleration --- src/libslic3r/GCode/GCodeProcessor.cpp | 39 +++++++++++++++++++++----- src/libslic3r/GCode/GCodeProcessor.hpp | 7 ++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 0a10ec4cc09..74831e36ed2 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -26,6 +26,7 @@ static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 +static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f; namespace Slic3r { @@ -190,6 +191,8 @@ void GCodeProcessor::TimeMachine::reset() enabled = false; acceleration = 0.0f; max_acceleration = 0.0f; + travel_acceleration = 0.0f; + max_travel_acceleration = 0.0f; extrude_factor_override_percentage = 1.0f; time = 0.0f; #if ENABLE_EXTENDED_M73_LINES @@ -842,6 +845,9 @@ void GCodeProcessor::apply_config(const PrintConfig& config) float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; + float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); + m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; + m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; } m_time_processor.export_remaining_time_enabled = config.remaining_times.value; @@ -1008,6 +1014,9 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; + float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i); + m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration; + m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; } if (m_time_processor.machine_limits.machine_max_acceleration_x.values.size() > 1) @@ -2230,9 +2239,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } // calculates block acceleration - float acceleration = is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i)); + float acceleration = + (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : + (is_extrusion_only_move(delta_pos) ? + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i))); for (unsigned char a = X; a <= E; ++a) { float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); @@ -2619,11 +2630,9 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) set_acceleration(static_cast(i), value); if (line.has_value('R', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); - if (line.has_value('T', value)) { + if (line.has_value('T', value)) // Interpret the T value as the travel acceleration in the new Marlin format. - //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value. - // set_travel_acceleration(value); - } + set_travel_acceleration(static_cast(i), value); } } } @@ -2894,6 +2903,22 @@ void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mo } } +float GCodeProcessor::get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +{ + size_t id = static_cast(mode); + return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; +} + +void GCodeProcessor::set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) +{ + size_t id = static_cast(mode); + if (id < m_time_processor.machines.size()) { + m_time_processor.machines[id].travel_acceleration = (m_time_processor.machines[id].max_travel_acceleration == 0.0f) ? value : + // Clamp the acceleration with the maximum. + std::min(value, m_time_processor.machines[id].max_travel_acceleration); + } +} + float GCodeProcessor::get_filament_load_time(size_t extruder_id) { return (m_time_processor.filament_load_times.empty() || m_time_processor.extruder_unloaded) ? diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 96e2160205c..cf55bf86e79 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -251,6 +251,9 @@ namespace Slic3r { float acceleration; // mm/s^2 // hard limit for the acceleration, to which the firmware will clamp. float max_acceleration; // mm/s^2 + float travel_acceleration; // mm/s^2 + // hard limit for the travel acceleration, to which the firmware will clamp. + float max_travel_acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s #if ENABLE_EXTENDED_M73_LINES @@ -668,7 +671,9 @@ namespace Slic3r { float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); + void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); + float get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; + void set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); float get_filament_load_time(size_t extruder_id); float get_filament_unload_time(size_t extruder_id); From ecf048ad8491730fd7efcfdc3476cea18393ec42 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 1 Apr 2021 11:30:41 +0200 Subject: [PATCH 020/154] An attempt to fix time estimates for 'Marlin (legacy)' flavor Old M204 S sets both printing and travel accelerations, which must be accounted for now when the latter was separated. --- src/libslic3r/GCode/GCodeProcessor.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 74831e36ed2..7a179097144 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -826,8 +826,13 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]); } - if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { m_time_processor.machine_limits = reinterpret_cast(config); + if (m_flavor == gcfMarlinLegacy) { + // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. + m_time_processor.machine_limits.machine_max_acceleration_travel = m_time_processor.machine_limits.machine_max_acceleration_extruding; + } + } // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they @@ -997,10 +1002,15 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_max_acceleration_retracting != nullptr) m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; - const ConfigOptionFloats* machine_max_acceleration_travel = config.option("machine_max_acceleration_travel"); + + // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. + const ConfigOptionFloats* machine_max_acceleration_travel = config.option(m_flavor == gcfMarlinLegacy + ? "machine_max_acceleration_extruding" + : "machine_max_acceleration_travel"); if (machine_max_acceleration_travel != nullptr) m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values; + const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); if (machine_min_extruding_rate != nullptr) m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; @@ -2617,10 +2627,11 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_value('S', value)) { - // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware, - // and it is also generated by Slic3r to control acceleration per extrusion type - // (there is a separate acceleration settings in Slicer for perimeter, first layer etc). + // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware + // It is also generated by PrusaSlicer to control acceleration per extrusion type + // (perimeters, first layer etc) when 'Marlin (legacy)' flavor is used. set_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); if (line.has_value('T', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); } From 424e1233fa4c188fb3db866b191e3867dd6c8e4b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 6 Apr 2021 22:20:24 +0200 Subject: [PATCH 021/154] MSW specific: Fixed update of the UI after system color change. Note: the wxEVT_SYS_COLOUR_CHANGED event works only for high contrast settings under MSW. + ConfigSnapshotDialog: Fixed UI colors for dark mode on all platforms --- src/slic3r/GUI/ConfigSnapshotDialog.cpp | 24 +++++++++++++++++------- src/slic3r/GUI/MainFrame.cpp | 5 +++++ src/slic3r/GUI/Plater.cpp | 6 ++++++ src/slic3r/GUI/Preferences.cpp | 3 +++ src/slic3r/GUI/Tab.cpp | 4 +++- src/slic3r/GUI/wxExtensions.cpp | 6 ++++++ 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 9f996d378bd..6a02e675349 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -29,11 +29,20 @@ static wxString format_reason(const Config::Snapshot::Reason reason) } } -static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active) +static std::string get_color(wxColour colour) { + wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); + return clr_str.ToStdString(); +}; + + +static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active, bool dark_mode) +{ // Start by declaring a row with an alternating background color. wxString text = ""; text += ""; @@ -92,14 +101,15 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot) { + bool dark_mode = wxGetApp().dark_mode(); wxString text = "" - "" - ""; + "" + ""; text += ""; for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) { const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1]; - text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot); + text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot, dark_mode); } text += "
" @@ -115,8 +125,8 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) { this->SetFont(wxGetApp().normal_font()); - this->SetBackgroundColour(*wxWHITE); - + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); this->SetSizer(vsizer); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 35b1c16d82a..5c390b66f67 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -522,6 +522,8 @@ void MainFrame::init_tabpanel() #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); #endif + if (wxSystemSettings::GetAppearance().IsDark()) + m_tabpanel->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); m_tabpanel->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); @@ -838,6 +840,9 @@ void MainFrame::on_sys_color_changed() // update label colors in respect to the system mode wxGetApp().init_label_colours(); +#ifdef __WXMSW__ + m_tabpanel->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // update Plater wxGetApp().plater()->sys_color_changed(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4b025cd2e6..a640d6e280e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -964,6 +964,10 @@ void Sidebar::msw_rescale() void Sidebar::sys_color_changed() { +#ifdef __WXMSW__ + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif + for (PlaterPresetComboBox* combo : std::vector{ p->combo_print, p->combo_sla_print, p->combo_sla_material, @@ -972,6 +976,8 @@ void Sidebar::sys_color_changed() for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); + p->frequently_changed_parameters->msw_rescale(); + p->object_list->msw_rescale(); p->object_list->sys_color_changed(); p->object_manipulation->sys_color_changed(); p->object_layers->sys_color_changed(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 67381cf2200..38a31bf76bc 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -51,6 +51,9 @@ void PreferencesDialog::build() auto app_config = get_app_config(); wxNotebook* tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); +#ifdef __WXMSW__ + tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // Add "General" tab m_optgroup_general = create_options_tab(_L("General"), tabs); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 933ae1a4dbd..cfd36e6878c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1002,7 +1002,9 @@ void Tab::sys_color_changed() for (ScalableBitmap& bmp : m_scaled_icons_list) m_icons->Add(bmp.bmp()); m_treectrl->AssignImageList(m_icons); - +#ifdef __WXMSW__ + m_treectrl->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // Colors for ui "decoration" update_label_colours(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 28c3ba40b9f..c8b0dfd31d2 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -590,6 +590,9 @@ void LockButton::msw_rescale() void LockButton::update_button_bitmaps() { +#ifdef __WXMSW__ + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif SetBitmap(m_is_pushed ? m_bmp_lock_closed.bmp() : m_bmp_lock_open.bmp()); SetBitmapHover(m_is_pushed ? m_bmp_lock_closed_f.bmp() : m_bmp_lock_open_f.bmp()); @@ -885,6 +888,9 @@ void ScalableButton::UseDefaultBitmapDisabled() void ScalableButton::msw_rescale() { +#ifdef __WXMSW__ + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt)); if (!m_disabled_icon_name.empty()) SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt)); From 27c779cc16031afe27189e5a25985d0f56602c44 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 7 Apr 2021 12:37:49 +0200 Subject: [PATCH 022/154] Fix issue with importing sl1 files with non-ascii filenames. --- src/libslic3r/Format/SL1.cpp | 2 +- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index f0485518268..64cb8b81547 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -87,7 +87,7 @@ PNGBuffer read_png(const mz_zip_archive_file_stat &entry, } ArchiveData extract_sla_archive(const std::string &zipfname, - const std::string &exclude) + const std::string &exclude) { ArchiveData arch; diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index f9fbef8a8f7..d9f261fce5b 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -178,7 +178,7 @@ void SLAImportJob::prepare() if (dlg.ShowModal() == wxID_OK) { auto path = dlg.get_path(); auto nm = wxFileName(path); - p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8(); + p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); p->sel = dlg.get_selection(); p->win = dlg.get_marchsq_windowsize(); } else { From f5ba2a144123675e66ae73319214735ce2b8d7c7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 7 Apr 2021 12:40:33 +0200 Subject: [PATCH 023/154] Fix duplicated error message dialog from GUI jobs. --- src/slic3r/GUI/Jobs/Job.cpp | 29 +++++++++++++++++++++++----- src/slic3r/GUI/Jobs/Job.hpp | 3 ++- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 3 ++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp index 1f7f58875b8..6346227aab2 100644 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ b/src/slic3r/GUI/Jobs/Job.cpp @@ -24,7 +24,7 @@ void GUI::Job::run(std::exception_ptr &eptr) void GUI::Job::update_status(int st, const wxString &msg) { - auto evt = new wxThreadEvent(); + auto evt = new wxThreadEvent(wxEVT_THREAD, m_thread_evt_id); evt->SetInt(st); evt->SetString(msg); wxQueueEvent(this, evt); @@ -33,7 +33,11 @@ void GUI::Job::update_status(int st, const wxString &msg) GUI::Job::Job(std::shared_ptr pri) : m_progress(std::move(pri)) { - Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { + m_thread_evt_id = wxNewId(); + + Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { + if (m_finalizing) return; + auto msg = evt.GetString(); if (!msg.empty() && !m_worker_error) m_progress->set_status_text(msg.ToUTF8().data()); @@ -53,13 +57,27 @@ GUI::Job::Job(std::shared_ptr pri) m_progress->set_progress(m_range); on_exception(m_worker_error); } - else + else { + // This is an RAII solution to remember that finalization is + // running. The run method calls update_status(status_range(), "") + // at the end, which queues up a call to this handler in all cases. + // If process also calls update_status with maxed out status arg + // it will call this handler twice. It is not a problem unless + // yield is called inside the finilize() method, which would + // jump out of finalize and call this handler again. + struct Finalizing { + bool &flag; + Finalizing (bool &f): flag(f) { flag = true; } + ~Finalizing() { flag = false; } + } fin(m_finalizing); + finalize(); + } // dont do finalization again for the same process m_finalized = true; } - }); + }, m_thread_evt_id); } void GUI::Job::start() @@ -76,7 +94,8 @@ void GUI::Job::start() m_progress->set_cancel_callback( [this]() { m_canceled.store(true); }); - m_finalized = false; + m_finalized = false; + m_finalizing = false; // Changing cursor to busy wxBeginBusyCursor(); diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index cb73fe74d40..082e5f4e8b5 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -29,9 +29,10 @@ namespace Slic3r { namespace GUI { class Job : public wxEvtHandler { int m_range = 100; + int m_thread_evt_id = wxID_ANY; boost::thread m_thread; std::atomic m_running{false}, m_canceled{false}; - bool m_finalized = false; + bool m_finalized = false, m_finalizing = false; std::shared_ptr m_progress; std::exception_ptr m_worker_error = nullptr; diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index d9f261fce5b..3da937f0ac2 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -132,7 +132,8 @@ SLAImportJob::~SLAImportJob() = default; void SLAImportJob::process() { auto progr = [this](int s) { - if (s < 100) update_status(int(s), _(L("Importing SLA archive"))); + if (s < 100) + update_status(int(s), _(L("Importing SLA archive"))); return !was_canceled(); }; From 44076d12db1ec8165c2b9d58472e0419d5037b9b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 4 Mar 2021 14:50:59 +0100 Subject: [PATCH 024/154] Reverting to old rotation optimizer object-function. Keep the performance optimizations though --- src/libslic3r/SLA/Rotfinder.cpp | 223 ++++--------------------- src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 14 -- 2 files changed, 31 insertions(+), 206 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 702690c196d..6caea2393a4 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -13,33 +13,11 @@ #include +#include + namespace Slic3r { namespace sla { -inline bool is_on_floor(const SLAPrintObject &mo) -{ - auto opt_elevation = mo.config().support_object_elevation.getFloat(); - auto opt_padaround = mo.config().pad_around_object.getBool(); - - return opt_elevation < EPSILON || opt_padaround; -} - -// Find transformed mesh ground level without copy and with parallel reduce. -double find_ground_level(const TriangleMesh &mesh, - const Transform3d & tr, - size_t threads) -{ - size_t vsize = mesh.its.vertices.size(); - - auto minfn = [](double a, double b) { return std::min(a, b); }; - - auto accessfn = [&mesh, &tr] (size_t vi) { - return (tr * mesh.its.vertices[vi].template cast()).z(); - }; - - double zmin = std::numeric_limits::max(); - size_t granularity = vsize / threads; - return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity); -} +namespace { // Get the vertices of a triangle directly in an array of 3 points std::array get_triangle_vertices(const TriangleMesh &mesh, @@ -74,33 +52,13 @@ struct Facestats { } }; -inline const Vec3d DOWN = {0., 0., -1.}; -constexpr double POINTS_PER_UNIT_AREA = 1.; - -// The score function for a particular face -inline double get_score(const Facestats &fc) -{ - // Simply get the angle (acos of dot product) between the face normal and - // the DOWN vector. - double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI; - - // Only consider faces that have have slopes below 90 deg: - phi = phi * (phi > 0.5); - - // Make the huge slopes more significant than the smaller slopes - phi = phi * phi * phi; - - // Multiply with the area of the current face - return fc.area * POINTS_PER_UNIT_AREA * phi; -} - template double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) { double initv = 0.; - auto mergefn = std::plus{}; - size_t grainsize = facecount / Nthreads; - size_t from = 0, to = facecount; + auto mergefn = [](double a, double b) { return a + b; }; + size_t grainsize = facecount / Nthreads; + size_t from = 0, to = facecount; return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize); } @@ -112,36 +70,18 @@ double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr) auto accessfn = [&mesh, &tr](size_t fi) { Facestats fc{get_transformed_triangle(mesh, tr, fi)}; - return get_score(fc); + + // We should score against the alignment with the reference planes + return std::abs(fc.normal.dot(Vec3d::UnitX())) + + std::abs(fc.normal.dot(Vec3d::UnitY())) + + std::abs(fc.normal.dot(Vec3d::UnitZ())); }; size_t facecount = mesh.its.indices.size(); size_t Nthreads = std::thread::hardware_concurrency(); - return sum_score(accessfn, facecount, Nthreads) / facecount; -} + double S = sum_score(accessfn, facecount, Nthreads); -double get_model_supportedness_onfloor(const TriangleMesh &mesh, - const Transform3d & tr) -{ - if (mesh.its.vertices.empty()) return std::nan(""); - - size_t Nthreads = std::thread::hardware_concurrency(); - - double zmin = find_ground_level(mesh, tr, Nthreads); - double zlvl = zmin + 0.1; // Set up a slight tolerance from z level - - auto accessfn = [&mesh, &tr, zlvl](size_t fi) { - std::array tri = get_transformed_triangle(mesh, tr, fi); - Facestats fc{tri}; - - if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl) - return -fc.area * POINTS_PER_UNIT_AREA; - - return get_score(fc); - }; - - size_t facecount = mesh.its.indices.size(); - return sum_score(accessfn, facecount, Nthreads) / facecount; + return S / facecount; } using XYRotation = std::array; @@ -155,88 +95,7 @@ Transform3d to_transform3d(const XYRotation &rot) return rt; } -XYRotation from_transform3d(const Transform3d &tr) -{ - Vec3d rot3d = Geometry::Transformation {tr}.get_rotation(); - return {rot3d.x(), rot3d.y()}; -} - -// Find the best score from a set of function inputs. Evaluate for every point. -template -std::array find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn) -{ - std::array ret = {}; - - double score = std::numeric_limits::max(); - - size_t Nthreads = std::thread::hardware_concurrency(); - size_t dist = std::distance(from, to); - std::vector scores(dist, score); - - ccr_par::for_each(size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) { - if (stopfn()) return; - - scores[i] = fn(*(from + i)); - }, dist / Nthreads); - - auto it = std::min_element(scores.begin(), scores.end()); - - if (it != scores.end()) ret = *(from + std::distance(scores.begin(), it)); - - return ret; -} - -// collect the rotations for each face of the convex hull -std::vector get_chull_rotations(const TriangleMesh &mesh, size_t max_count) -{ - TriangleMesh chull = mesh.convex_hull_3d(); - chull.require_shared_vertices(); - double chull2d_area = chull.convex_hull().area(); - double area_threshold = chull2d_area / (scaled(1e3) * scaled(1.)); - - size_t facecount = chull.its.indices.size(); - - struct RotArea { XYRotation rot; double area; }; - - auto inputs = reserve_vector(facecount); - - auto rotcmp = [](const RotArea &r1, const RotArea &r2) { - double xdiff = r1.rot[X] - r2.rot[X], ydiff = r1.rot[Y] - r2.rot[Y]; - return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.; - }; - - auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) { - double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y]; - return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON; - }; - - for (size_t fi = 0; fi < facecount; ++fi) { - Facestats fc{get_triangle_vertices(chull, fi)}; - - if (fc.area > area_threshold) { - auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN); - XYRotation rot = from_transform3d(Transform3d::Identity() * q); - RotArea ra = {rot, fc.area}; - - auto it = std::lower_bound(inputs.begin(), inputs.end(), ra, rotcmp); - - if (it == inputs.end() || !eqcmp(it->rot, rot)) - inputs.insert(it, ra); - } - } - - inputs.shrink_to_fit(); - if (!max_count) max_count = inputs.size(); - std::sort(inputs.begin(), inputs.end(), - [](const RotArea &ra, const RotArea &rb) { - return ra.area > rb.area; - }); - - auto ret = reserve_vector(std::min(max_count, inputs.size())); - for (const RotArea &ra : inputs) ret.emplace_back(ra.rot); - - return ret; -} +} // namespace Vec2d find_best_rotation(const SLAPrintObject & po, float accuracy, @@ -267,45 +126,26 @@ Vec2d find_best_rotation(const SLAPrintObject & po, statuscb(unsigned(++status * 100.0/max_tries) ); }; - // Different search methods have to be used depending on the model elevation - if (is_on_floor(po)) { + // Preparing the optimizer. + size_t gridsize = std::sqrt(max_tries); + opt::Optimizer solver(opt::StopCriteria{} + .max_iterations(max_tries) + .stop_condition(stopcond), + gridsize); - std::vector inputs = get_chull_rotations(mesh, max_tries); - max_tries = inputs.size(); + // We are searching rotations around only two axes x, y. Thus the + // problem becomes a 2 dimensional optimization task. + // We can specify the bounds for a dimension in the following way: + auto bounds = opt::bounds({ {-PI/2, PI/2}, {-PI/2, PI/2} }); - // If the model can be placed on the bed directly, we only need to - // check the 3D convex hull face rotations. - - auto objfn = [&mesh, &statusfn](const XYRotation &rot) { + auto result = solver.to_max().optimize( + [&mesh, &statusfn] (const XYRotation &rot) + { statusfn(); - Transform3d tr = to_transform3d(rot); - return get_model_supportedness_onfloor(mesh, tr); - }; + return get_model_supportedness(mesh, to_transform3d(rot)); + }, opt::initvals({0., 0.}), bounds); - rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond); - } else { - // Preparing the optimizer. - size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls - opt::Optimizer solver(opt::StopCriteria{} - .max_iterations(max_tries) - .stop_condition(stopcond), - gridsize); - - // We are searching rotations around only two axes x, y. Thus the - // problem becomes a 2 dimensional optimization task. - // We can specify the bounds for a dimension in the following way: - auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} }); - - auto result = solver.to_min().optimize( - [&mesh, &statusfn] (const XYRotation &rot) - { - statusfn(); - return get_model_supportedness(mesh, to_transform3d(rot)); - }, opt::initvals({0., 0.}), bounds); - - // Save the result and fck off - rot = result.optimum; - } + rot = result.optimum; return {rot[0], rot[1]}; } @@ -315,8 +155,7 @@ double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr) TriangleMesh mesh = po.model_object()->raw_mesh(); mesh.require_shared_vertices(); - return is_on_floor(po) ? get_model_supportedness_onfloor(mesh, tr) : - get_model_supportedness(mesh, tr); + return get_model_supportedness(mesh, tr); } }} // namespace Slic3r::sla diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 978ccf8fcf3..ac737cafb05 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -21,20 +21,6 @@ void RotoptimizeJob::process() if (!o || !po) return; - TriangleMesh mesh = o->raw_mesh(); - mesh.require_shared_vertices(); - -// for (auto inst : o->instances) { -// Transform3d tr = Transform3d::Identity(); -// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Z), Vec3d::UnitZ())); -// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Y), Vec3d::UnitY())); -// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(X), Vec3d::UnitX())); - -// double score = sla::get_model_supportedness(*po, tr); - -// std::cout << "Model supportedness before: " << score << std::endl; -// } - Vec2d r = sla::find_best_rotation(*po, 0.75f, [this](unsigned s) { if (s < 100) From 192269b2c7e9824b08d574254445e2e698f1a7bc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 17 Mar 2021 19:42:58 +0100 Subject: [PATCH 025/154] Add new execution framework Inspired by std::execution --- src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/Execution/Execution.hpp | 100 +++++++++++++++++++ src/libslic3r/Execution/ExecutionSeq.hpp | 84 ++++++++++++++++ src/libslic3r/Execution/ExecutionTBB.hpp | 77 +++++++++++++++ src/libslic3r/MTUtils.hpp | 9 +- src/libslic3r/SLA/Concurrency.hpp | 120 ++++------------------- 6 files changed, 285 insertions(+), 108 deletions(-) create mode 100644 src/libslic3r/Execution/Execution.hpp create mode 100644 src/libslic3r/Execution/ExecutionSeq.hpp create mode 100644 src/libslic3r/Execution/ExecutionTBB.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 4a762f7e151..2abe94656d8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -219,6 +219,9 @@ add_library(libslic3r STATIC SimplifyMeshImpl.hpp SimplifyMesh.cpp MarchingSquares.hpp + Execution/Execution.hpp + Execution/ExecutionSeq.hpp + Execution/ExecutionTBB.hpp Optimize/Optimizer.hpp Optimize/NLoptOptimizer.hpp Optimize/BruteforceOptimizer.hpp diff --git a/src/libslic3r/Execution/Execution.hpp b/src/libslic3r/Execution/Execution.hpp new file mode 100644 index 00000000000..809cc45d3c9 --- /dev/null +++ b/src/libslic3r/Execution/Execution.hpp @@ -0,0 +1,100 @@ +#ifndef EXECUTION_HPP +#define EXECUTION_HPP + +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" + +namespace Slic3r { + +template +using remove_cvref_t = std::remove_reference_t>; + +// Override for valid execution policies +template struct IsExecutionPolicy_ : public std::false_type {}; + +template constexpr bool IsExecutionPolicy = + IsExecutionPolicy_>::value; + +template +using ExecutionPolicyOnly = std::enable_if_t, T>; + +namespace execution { + +// This struct needs to be specialized for each execution policy. +// See ExecutionSeq.hpp and ExecutionTBB.hpp for example. +template struct Traits {}; + +template using AsTraits = Traits>; + +// Each execution policy should declare two types of mutexes. A a spin lock and +// a blocking mutex. +template using SpinningMutex = typename Traits::SpinningMutex; +template using BlockingMutex = typename Traits::BlockingMutex; + +// Query the available threads for concurrency. +template > +size_t max_concurrency(const EP &ep) +{ + return AsTraits::max_concurrency(ep); +} + +// foreach loop with the execution policy passed as argument. Granularity can +// be specified explicitly. max_concurrency() can be used for optimal results. +template> +void for_each(const EP &ep, It from, It to, Fn &&fn, size_t granularity = 1) +{ + AsTraits::for_each(ep, from, to, std::forward(fn), granularity); +} + +// A reduce operation with the execution policy passed as argument. +// mergefn has T(const T&, const T&) signature +// accessfn has T(I) signature if I is an integral type and +// T(const I::value_type &) if I is an iterator type. +template > +T reduce(const EP & ep, + I from, + I to, + const T & init, + MergeFn && mergefn, + AccessFn &&accessfn, + size_t granularity = 1) +{ + return AsTraits::reduce(ep, from, to, init, + std::forward(mergefn), + std::forward(accessfn), + granularity); +} + +// An overload of reduce method to be used with iterators as 'from' and 'to' +// arguments. +template, + class = IteratorOnly > +T reduce(const EP &ep, + I from, + I to, + const T & init, + MergeFn &&mergefn, + size_t granularity = 1) +{ + return reduce( + ep, from, to, init, std::forward(mergefn), + [](typename I::value_type &i) { return i; }, granularity); +} + +} // namespace execution_policy +} // namespace Slic3r + +#endif // EXECUTION_HPP diff --git a/src/libslic3r/Execution/ExecutionSeq.hpp b/src/libslic3r/Execution/ExecutionSeq.hpp new file mode 100644 index 00000000000..321d65631bb --- /dev/null +++ b/src/libslic3r/Execution/ExecutionSeq.hpp @@ -0,0 +1,84 @@ +#ifndef EXECUTIONSEQ_HPP +#define EXECUTIONSEQ_HPP + +#ifdef PRUSASLICER_USE_EXECUTION_STD // Conflicts with our version of TBB +#include +#endif + +#include "Execution.hpp" + +namespace Slic3r { + +// Execution policy implementing dummy sequential algorithms +struct ExecutionSeq {}; + +template<> struct IsExecutionPolicy_ : public std::true_type {}; + +static constexpr ExecutionSeq ex_seq = {}; + +template struct IsSequentialEP_ { static constexpr bool value = false; }; + +template<> struct IsSequentialEP_: public std::true_type {}; +#ifdef PRUSASLICER_USE_EXECUTION_STD +template<> struct IsExecutionPolicy_: public std::true_type {}; +template<> struct IsSequentialEP_: public std::true_type {}; +#endif + +template +constexpr bool IsSequentialEP = IsSequentialEP_>::value; + +template +using SequentialEPOnly = std::enable_if_t, R>; + +template +struct execution::Traits> { +private: + struct _Mtx { inline void lock() {} inline void unlock() {} }; + + template + static IteratorOnly loop_(It from, It to, Fn &&fn) + { + for (auto it = from; it != to; ++it) fn(*it); + } + + template + static IntegerOnly loop_(I from, I to, Fn &&fn) + { + for (I i = from; i < to; ++i) fn(i); + } + +public: + using SpinningMutex = _Mtx; + using BlockingMutex = _Mtx; + + template + static void for_each(const EP &, + It from, + It to, + Fn &&fn, + size_t /* ignore granularity */ = 1) + { + loop_(from, to, std::forward(fn)); + } + + template + static T reduce(const EP &, + I from, + I to, + const T & init, + MergeFn &&mergefn, + AccessFn &&access, + size_t /*granularity*/ = 1 + ) + { + T acc = init; + loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); }); + return acc; + } + + static size_t max_concurrency(const EP &) { return 1; } +}; + +} // namespace Slic3r + +#endif // EXECUTIONSEQ_HPP diff --git a/src/libslic3r/Execution/ExecutionTBB.hpp b/src/libslic3r/Execution/ExecutionTBB.hpp new file mode 100644 index 00000000000..cf6373c4663 --- /dev/null +++ b/src/libslic3r/Execution/ExecutionTBB.hpp @@ -0,0 +1,77 @@ +#ifndef EXECUTIONTBB_HPP +#define EXECUTIONTBB_HPP + +#include +#include +#include +#include +#include + +#include "Execution.hpp" + +namespace Slic3r { + +struct ExecutionTBB {}; +template<> struct IsExecutionPolicy_ : public std::true_type {}; + +// Execution policy using Intel TBB library under the hood. +static constexpr ExecutionTBB ex_tbb = {}; + +template<> struct execution::Traits { +private: + + template + static IteratorOnly loop_(const tbb::blocked_range &range, Fn &&fn) + { + for (auto &el : range) fn(el); + } + + template + static IntegerOnly loop_(const tbb::blocked_range &range, Fn &&fn) + { + for (I i = range.begin(); i < range.end(); ++i) fn(i); + } + +public: + using SpinningMutex = tbb::spin_mutex; + using BlockingMutex = tbb::mutex; + + template + static void for_each(const ExecutionTBB &, + It from, It to, Fn &&fn, size_t granularity) + { + tbb::parallel_for(tbb::blocked_range{from, to, granularity}, + [&fn](const auto &range) { + loop_(range, std::forward(fn)); + }); + } + + template + static T reduce(const ExecutionTBB &, + I from, + I to, + const T &init, + MergeFn &&mergefn, + AccessFn &&access, + size_t granularity = 1 + ) + { + return tbb::parallel_reduce( + tbb::blocked_range{from, to, granularity}, init, + [&](const auto &range, T subinit) { + T acc = subinit; + loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); }); + return acc; + }, + std::forward(mergefn)); + } + + static size_t max_concurrency(const ExecutionTBB &) + { + return tbb::this_task_arena::max_concurrency(); + } +}; + +} + +#endif // EXECUTIONTBB_HPP diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 555cfe50191..7b903f66c8f 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -106,13 +106,8 @@ template bool all_of(const C &container) }); } -template struct remove_cvref -{ - using type = - typename std::remove_cv::type>::type; -}; - -template using remove_cvref_t = typename remove_cvref::type; +template +using remove_cvref_t = std::remove_reference_t>; /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html template> diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index 8ff0ff809ea..7299101b313 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -1,16 +1,10 @@ #ifndef SLA_CONCURRENCY_H #define SLA_CONCURRENCY_H -#include -#include -#include -#include -#include +// FIXME: Deprecated -#include -#include - -#include +#include +#include namespace Slic3r { namespace sla { @@ -23,124 +17,48 @@ template struct _ccr {}; template<> struct _ccr { - using SpinningMutex = tbb::spin_mutex; - using BlockingMutex = tbb::mutex; - - template - static IteratorOnly loop_(const tbb::blocked_range &range, Fn &&fn) - { - for (auto &el : range) fn(el); - } - - template - static IntegerOnly loop_(const tbb::blocked_range &range, Fn &&fn) - { - for (I i = range.begin(); i < range.end(); ++i) fn(i); - } + using SpinningMutex = execution::SpinningMutex; + using BlockingMutex = execution::BlockingMutex; template static void for_each(It from, It to, Fn &&fn, size_t granularity = 1) { - tbb::parallel_for(tbb::blocked_range{from, to, granularity}, - [&fn](const auto &range) { - loop_(range, std::forward(fn)); - }); + execution::for_each(ex_tbb, from, to, std::forward(fn), granularity); } - template - static T reduce(I from, - I to, - const T &init, - MergeFn &&mergefn, - AccessFn &&access, - size_t granularity = 1 - ) + template + static auto reduce(Args&&...args) { - return tbb::parallel_reduce( - tbb::blocked_range{from, to, granularity}, init, - [&](const auto &range, T subinit) { - T acc = subinit; - loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); }); - return acc; - }, - std::forward(mergefn)); - } - - template - static IteratorOnly reduce(I from, - I to, - const T & init, - MergeFn &&mergefn, - size_t granularity = 1) - { - return reduce( - from, to, init, std::forward(mergefn), - [](typename I::value_type &i) { return i; }, granularity); + return execution::reduce(ex_tbb, std::forward(args)...); } static size_t max_concurreny() { - return tbb::this_task_arena::max_concurrency(); + return execution::max_concurrency(ex_tbb); } }; template<> struct _ccr { -private: - struct _Mtx { inline void lock() {} inline void unlock() {} }; - -public: - using SpinningMutex = _Mtx; - using BlockingMutex = _Mtx; - - template - static IteratorOnly loop_(It from, It to, Fn &&fn) - { - for (auto it = from; it != to; ++it) fn(*it); - } - - template - static IntegerOnly loop_(I from, I to, Fn &&fn) - { - for (I i = from; i < to; ++i) fn(i); - } + using SpinningMutex = execution::SpinningMutex; + using BlockingMutex = execution::BlockingMutex; template - static void for_each(It from, - It to, - Fn &&fn, - size_t /* ignore granularity */ = 1) + static void for_each(It from, It to, Fn &&fn, size_t granularity = 1) { - loop_(from, to, std::forward(fn)); + execution::for_each(ex_seq, from, to, std::forward(fn), granularity); } - template - static T reduce(I from, - I to, - const T & init, - MergeFn &&mergefn, - AccessFn &&access, - size_t /*granularity*/ = 1 - ) + template + static auto reduce(Args&&...args) { - T acc = init; - loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); }); - return acc; + return execution::reduce(ex_seq, std::forward(args)...); } - template - static IteratorOnly reduce(I from, - I to, - const T &init, - MergeFn &&mergefn, - size_t /*granularity*/ = 1 - ) + static size_t max_concurreny() { - return reduce(from, to, init, std::forward(mergefn), - [](typename I::value_type &i) { return i; }); + return execution::max_concurrency(ex_seq); } - - static size_t max_concurreny() { return 1; } }; using ccr = _ccr; From a8420ca83c0b74312ffd664d17e6fb75e90b6fe6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 4 Mar 2021 18:15:13 +0100 Subject: [PATCH 026/154] Speed up rotation optimizer - No float to double conversion - Solving issue of random (very similar) results due to the parallel summation of floats --- src/libslic3r/SLA/Rotfinder.cpp | 85 +++++++++++++++++---------------- src/libslic3r/SLA/Rotfinder.hpp | 2 +- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 6caea2393a4..f410eb0e14b 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -1,7 +1,9 @@ #include #include -#include + +#include +#include #include @@ -20,66 +22,58 @@ namespace Slic3r { namespace sla { namespace { // Get the vertices of a triangle directly in an array of 3 points -std::array get_triangle_vertices(const TriangleMesh &mesh, +std::array get_triangle_vertices(const TriangleMesh &mesh, size_t faceidx) { const auto &face = mesh.its.indices[faceidx]; - return {Vec3d{mesh.its.vertices[face(0)].cast()}, - Vec3d{mesh.its.vertices[face(1)].cast()}, - Vec3d{mesh.its.vertices[face(2)].cast()}}; + return {mesh.its.vertices[face(0)], + mesh.its.vertices[face(1)], + mesh.its.vertices[face(2)]}; } -std::array get_transformed_triangle(const TriangleMesh &mesh, - const Transform3d & tr, +std::array get_transformed_triangle(const TriangleMesh &mesh, + const Transform3f & tr, size_t faceidx) { const auto &tri = get_triangle_vertices(mesh, faceidx); return {tr * tri[0], tr * tri[1], tr * tri[2]}; } -// Get area and normal of a triangle -struct Facestats { - Vec3d normal; - double area; - - explicit Facestats(const std::array &triangle) - { - Vec3d U = triangle[1] - triangle[0]; - Vec3d V = triangle[2] - triangle[0]; - Vec3d C = U.cross(V); - normal = C.normalized(); - area = 0.5 * C.norm(); - } -}; - -template -double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) +template Vec<3, T> normal(const std::array, 3> &tri) { - double initv = 0.; - auto mergefn = [](double a, double b) { return a + b; }; - size_t grainsize = facecount / Nthreads; - size_t from = 0, to = facecount; + Vec<3, T> U = tri[1] - tri[0]; + Vec<3, T> V = tri[2] - tri[0]; + return U.cross(V).normalized(); +} - return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize); +template +T sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) +{ + T initv = 0.; + auto mergefn = [](T a, T b) { return a + b; }; + size_t grainsize = facecount / Nthreads; + size_t from = 0, to = facecount; + + return execution::reduce(ex_seq, from, to, initv, mergefn, accessfn, grainsize); } // Try to guess the number of support points needed to support a mesh -double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr) +double get_model_supportedness(const TriangleMesh &mesh, const Transform3f &tr) { if (mesh.its.vertices.empty()) return std::nan(""); auto accessfn = [&mesh, &tr](size_t fi) { - Facestats fc{get_transformed_triangle(mesh, tr, fi)}; + Vec3f n = normal(get_transformed_triangle(mesh, tr, fi)); // We should score against the alignment with the reference planes - return std::abs(fc.normal.dot(Vec3d::UnitX())) + - std::abs(fc.normal.dot(Vec3d::UnitY())) + - std::abs(fc.normal.dot(Vec3d::UnitZ())); + return scaled(std::abs(n.dot(Vec3f::UnitX())) + + std::abs(n.dot(Vec3f::UnitY())) + + std::abs(n.dot(Vec3f::UnitZ()))); }; size_t facecount = mesh.its.indices.size(); size_t Nthreads = std::thread::hardware_concurrency(); - double S = sum_score(accessfn, facecount, Nthreads); + double S = unscaled(sum_score(accessfn, facecount, Nthreads)); return S / facecount; } @@ -87,11 +81,12 @@ double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr) using XYRotation = std::array; // prepare the rotation transformation -Transform3d to_transform3d(const XYRotation &rot) +Transform3f to_transform3f(const XYRotation &rot) { - Transform3d rt = Transform3d::Identity(); - rt.rotate(Eigen::AngleAxisd(rot[1], Vec3d::UnitY())); - rt.rotate(Eigen::AngleAxisd(rot[0], Vec3d::UnitX())); + Transform3f rt = Transform3f::Identity(); + rt.rotate(Eigen::AngleAxisf(float(rot[1]), Vec3f::UnitY())); + rt.rotate(Eigen::AngleAxisf(float(rot[0]), Vec3f::UnitX())); + return rt; } @@ -138,19 +133,27 @@ Vec2d find_best_rotation(const SLAPrintObject & po, // We can specify the bounds for a dimension in the following way: auto bounds = opt::bounds({ {-PI/2, PI/2}, {-PI/2, PI/2} }); + Benchmark bench; + + bench.start(); auto result = solver.to_max().optimize( [&mesh, &statusfn] (const XYRotation &rot) { statusfn(); - return get_model_supportedness(mesh, to_transform3d(rot)); + return get_model_supportedness(mesh, to_transform3f(rot)); }, opt::initvals({0., 0.}), bounds); + bench.stop(); rot = result.optimum; + std::cout << "Optimum score: " << result.score << std::endl; + std::cout << "Optimum rotation: " << result.optimum[0] << " " << result.optimum[1] << std::endl; + std::cout << "Optimization took: " << bench.getElapsedSec() << " seconds" << std::endl; + return {rot[0], rot[1]}; } -double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr) +double get_model_supportedness(const SLAPrintObject &po, const Transform3f &tr) { TriangleMesh mesh = po.model_object()->raw_mesh(); mesh.require_shared_vertices(); diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 96561a890f9..a6fde2c9d92 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -35,7 +35,7 @@ Vec2d find_best_rotation( ); double get_model_supportedness(const SLAPrintObject &mesh, - const Transform3d & tr); + const Transform3f & tr); } // namespace sla } // namespace Slic3r From c9b787af674d0299febfc0e14555b3b4576ebb0b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Mar 2021 09:37:46 +0100 Subject: [PATCH 027/154] Unite cancel callback and status function --- src/libslic3r/SLA/Rotfinder.cpp | 15 +++++++++------ src/libslic3r/SLA/Rotfinder.hpp | 12 ++++++------ src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 11 +++++------ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index f410eb0e14b..79be5e1ec45 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -92,10 +92,9 @@ Transform3f to_transform3f(const XYRotation &rot) } // namespace -Vec2d find_best_rotation(const SLAPrintObject & po, - float accuracy, - std::function statuscb, - std::function stopcond) +Vec2d find_best_rotation(const SLAPrintObject & po, + float accuracy, + std::function statuscb) { static const unsigned MAX_TRIES = 1000; @@ -108,7 +107,7 @@ Vec2d find_best_rotation(const SLAPrintObject & po, mesh.require_shared_vertices(); // To keep track of the number of iterations - unsigned status = 0; + int status = 0; // The maximum number of iterations auto max_tries = unsigned(accuracy * MAX_TRIES); @@ -118,7 +117,11 @@ Vec2d find_best_rotation(const SLAPrintObject & po, auto statusfn = [&statuscb, &status, &max_tries] { // report status - statuscb(unsigned(++status * 100.0/max_tries) ); + statuscb(++status * 100.0/max_tries); + }; + + auto stopcond = [&statuscb] { + return ! statuscb(-1); }; // Preparing the optimizer. diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index a6fde2c9d92..2b92c52b8dc 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -19,19 +19,19 @@ namespace sla { * @param accuracy The optimization accuracy from 0.0f to 1.0f. Currently, * the nlopt genetic optimizer is used and the number of iterations is * accuracy * 100000. This can change in the future. - * @param statuscb A status indicator callback called with the unsigned + * @param statuscb A status indicator callback called with the int * argument spanning from 0 to 100. May not reach 100 if the optimization finds - * an optimum before max iterations are reached. - * @param stopcond A function that if returns true, the search process will be - * terminated and the best solution found will be returned. + * an optimum before max iterations are reached. It should return a boolean + * signaling if the operation may continue (true) or not (false). A status + * value lower than 0 shall not update the status but still return a valid + * continuation indicator. * * @return Returns the rotations around each axis (x, y, z) */ Vec2d find_best_rotation( const SLAPrintObject& modelobj, float accuracy = 1.0f, - std::function statuscb = [] (unsigned) {}, - std::function stopcond = [] () { return false; } + std::function statuscb = [] (int) { return true; } ); double get_model_supportedness(const SLAPrintObject &mesh, diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index ac737cafb05..7e1bfaeebd2 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -21,13 +21,12 @@ void RotoptimizeJob::process() if (!o || !po) return; - Vec2d r = sla::find_best_rotation(*po, 0.75f, - [this](unsigned s) { - if (s < 100) - update_status(int(s), _(L("Searching for optimal orientation"))); - }, - [this] () { return was_canceled(); }); + Vec2d r = sla::find_best_rotation(*po, 0.75f, [this](int s) { + if (s > 0 && s < 100) + update_status(s, _(L("Searching for optimal orientation"))); + return !was_canceled(); + }); double mindist = 6.0; // FIXME From a7851618c8381cce8c14f23b2a17c4b505c47f2f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Mar 2021 09:38:21 +0100 Subject: [PATCH 028/154] Extend execution framework with convenience functions --- src/libslic3r/Execution/Execution.hpp | 42 +++++++++++++++++++++++---- tests/sla_print/sla_print_tests.cpp | 2 +- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Execution/Execution.hpp b/src/libslic3r/Execution/Execution.hpp index 809cc45d3c9..e4bad9f2370 100644 --- a/src/libslic3r/Execution/Execution.hpp +++ b/src/libslic3r/Execution/Execution.hpp @@ -10,6 +10,7 @@ namespace Slic3r { +// Borrowed from C++20 template using remove_cvref_t = std::remove_reference_t>; @@ -31,7 +32,7 @@ template struct Traits {}; template using AsTraits = Traits>; // Each execution policy should declare two types of mutexes. A a spin lock and -// a blocking mutex. +// a blocking mutex. These types should satisfy the BasicLockable concept. template using SpinningMutex = typename Traits::SpinningMutex; template using BlockingMutex = typename Traits::BlockingMutex; @@ -75,13 +76,12 @@ T reduce(const EP & ep, } // An overload of reduce method to be used with iterators as 'from' and 'to' -// arguments. +// arguments. Access functor is omitted here. template, - class = IteratorOnly > + class = ExecutionPolicyOnly > T reduce(const EP &ep, I from, I to, @@ -91,7 +91,39 @@ T reduce(const EP &ep, { return reduce( ep, from, to, init, std::forward(mergefn), - [](typename I::value_type &i) { return i; }, granularity); + [](const auto &i) { return i; }, granularity); +} + +template> +T accumulate(const EP & ep, + I from, + I to, + const T & init, + AccessFn &&accessfn, + size_t granularity = 1) +{ + return reduce(ep, from, to, init, std::plus{}, + std::forward(accessfn), granularity); +} + + +template > +T accumulate(const EP &ep, + I from, + I to, + const T & init, + size_t granularity = 1) +{ + return reduce( + ep, from, to, init, std::plus{}, [](const auto &i) { return i; }, + granularity); } } // namespace execution_policy diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index bdd5731dcc2..59c841468d8 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -248,7 +248,7 @@ TEST_CASE("Test concurrency") double ref = std::accumulate(vals.begin(), vals.end(), 0.); - double s = sla::ccr_par::reduce(vals.begin(), vals.end(), 0., std::plus{}); + double s = execution::accumulate(ex_tbb, vals.begin(), vals.end(), 0.); REQUIRE(s == Approx(ref)); } From 97e7350911c48023a5cef6972faa69b0e6cb0b54 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Mar 2021 20:20:01 +0100 Subject: [PATCH 029/154] Method selection implemented --- src/libslic3r/SLA/Rotfinder.cpp | 14 +---- src/libslic3r/SLA/Rotfinder.hpp | 10 ++-- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 75 ++++++++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 34 +++++++++++ src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 23 +++++++- src/slic3r/GUI/Jobs/RotoptimizeJob.hpp | 43 +++++++++++++- 6 files changed, 181 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 79be5e1ec45..eb54b02dc26 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -92,9 +92,9 @@ Transform3f to_transform3f(const XYRotation &rot) } // namespace -Vec2d find_best_rotation(const SLAPrintObject & po, - float accuracy, - std::function statuscb) +Vec2d find_best_misalignment_rotation(const SLAPrintObject & po, + float accuracy, + std::function statuscb) { static const unsigned MAX_TRIES = 1000; @@ -156,12 +156,4 @@ Vec2d find_best_rotation(const SLAPrintObject & po, return {rot[0], rot[1]}; } -double get_model_supportedness(const SLAPrintObject &po, const Transform3f &tr) -{ - TriangleMesh mesh = po.model_object()->raw_mesh(); - mesh.require_shared_vertices(); - - return get_model_supportedness(mesh, tr); -} - }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 2b92c52b8dc..56884565fa8 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -9,9 +9,12 @@ namespace Slic3r { class SLAPrintObject; +class TriangleMesh; namespace sla { +using RotOptimizeStatusCB = std::function; + /** * The function should find the best rotation for SLA upside down printing. * @@ -28,14 +31,13 @@ namespace sla { * * @return Returns the rotations around each axis (x, y, z) */ -Vec2d find_best_rotation( +Vec2d find_best_misalignment_rotation( const SLAPrintObject& modelobj, float accuracy = 1.0f, - std::function statuscb = [] (int) { return true; } + RotOptimizeStatusCB statuscb = [] (int) { return true; } ); -double get_model_supportedness(const SLAPrintObject &mesh, - const Transform3f & tr); + } // namespace sla } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index c5060a88ed3..b44bc01f03d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -7,9 +7,10 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/Plater.hpp" #include "libslic3r/PresetBundle.hpp" -#include "libslic3r/SLA/Rotfinder.hpp" +#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" namespace Slic3r { namespace GUI { @@ -204,6 +205,23 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi { if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) return; + + RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}}; +} + +void GLGizmoRotate3D::load_rotoptimize_state() +{ + std::string accuracy_str = + wxGetApp().app_config->get("rotoptimize", "accuracy"); + + std::string method_str = + wxGetApp().app_config->get("rotoptimize", "method_id"); + + if (!accuracy_str.empty()) + m_rotoptimizewin_state.accuracy = std::stof(accuracy_str); + + if (!method_str.empty()) + m_rotoptimizewin_state.method_id = std::stoi(method_str); } void GLGizmoRotate::render_circle() const @@ -436,6 +454,9 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil { m_gizmos[i].set_group_id(i); } + + std::cout << "Load rotopt state" << std::endl; + load_rotoptimize_state(); } bool GLGizmoRotate3D::on_init() @@ -492,5 +513,57 @@ void GLGizmoRotate3D::on_render() const m_gizmos[Z].render(); } +const char * GLGizmoRotate3D::RotoptimzeWindow::options[RotoptimizeJob::get_methods_count()]; +bool GLGizmoRotate3D::RotoptimzeWindow::options_valid = false; + +GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &alignment) + : m_imgui{imgui} +{ + imgui->begin(_L("Optimize orientation"), ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + float win_h = ImGui::GetWindowHeight(); + float x = alignment.x, y = alignment.y; + y = std::min(y, alignment.bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + + ImGui::PushItemWidth(200.f); + + size_t methods_cnt = RotoptimizeJob::get_methods_count(); + if (!options_valid) { + for (size_t i = 0; i < methods_cnt; ++i) + options[i] = RotoptimizeJob::get_method_names()[i].c_str(); + + options_valid = true; + } + + int citem = state.method_id; + if (ImGui::Combo(_L("Choose method").c_str(), &citem, options, methods_cnt) ) { + state.method_id = citem; + wxGetApp().app_config->set("rotoptimize", "method_id", std::to_string(state.method_id)); + } + + float accuracy = state.accuracy; + if (imgui->slider_float(_L("Accuracy/Speed"), &accuracy, 0.01f, 1.f, "%.1f")) { + state.accuracy = accuracy; + wxGetApp().app_config->set("rotoptimize", "accuracy", std::to_string(state.accuracy)); + } + + ImGui::Separator(); + + if ( imgui->button(_L("Optimize")) ) { + wxGetApp().plater()->optimize_rotation(); + } +} + +GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow() +{ + m_imgui->end(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 126c97b1dd7..c18d0eefd9d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -2,6 +2,7 @@ #define slic3r_GLGizmoRotate_hpp_ #include "GLGizmoBase.hpp" +#include "../Jobs/RotoptimizeJob.hpp" namespace Slic3r { @@ -136,6 +137,39 @@ protected: } void on_render_input_window(float x, float y, float bottom_limit) override; + +private: + + class RotoptimzeWindow { + ImGuiWrapper *m_imgui = nullptr; + + static const char * options []; + static bool options_valid; + + public: + + struct State { + float accuracy = 1.f; + int method_id = 0; + }; + + struct Alignment { float x, y, bottom_limit; }; + + RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &bottom_limit); + + ~RotoptimzeWindow(); + + RotoptimzeWindow(const RotoptimzeWindow&) = delete; + RotoptimzeWindow(RotoptimzeWindow &&) = delete; + RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete; + RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete; + }; + + RotoptimzeWindow::State m_rotoptimizewin_state = {}; + + void load_rotoptimize_state(); }; } // namespace GUI diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 7e1bfaeebd2..2e83bbf962c 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -8,8 +8,29 @@ #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "libslic3r/AppConfig.hpp" + namespace Slic3r { namespace GUI { +void RotoptimizeJob::prepare() +{ + std::string accuracy_str = + wxGetApp().app_config->get("rotoptimize", "accuracy"); + + std::string method_str = + wxGetApp().app_config->get("rotoptimize", "method_id"); + + if (!accuracy_str.empty()) + m_accuracy = std::stof(accuracy_str); + + if (!method_str.empty()) + m_method_id = std::stoi(method_str); + + m_accuracy = std::max(0.f, std::min(m_accuracy, 1.f)); + m_method_id = std::max(size_t(0), std::min(get_methods_count() - 1, m_method_id)); +} + void RotoptimizeJob::process() { int obj_idx = m_plater->get_selected_object_idx(); @@ -21,7 +42,7 @@ void RotoptimizeJob::process() if (!o || !po) return; - Vec2d r = sla::find_best_rotation(*po, 0.75f, [this](int s) { + Vec2d r = Methods[m_method_id].findfn(*po, m_accuracy, [this](int s) { if (s > 0 && s < 100) update_status(s, _(L("Searching for optimal orientation"))); diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index 06688b52d90..35703644043 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -3,17 +3,58 @@ #include "PlaterJob.hpp" -namespace Slic3r { namespace GUI { +#include "libslic3r/SLA/Rotfinder.hpp" + +namespace Slic3r { + +class SLAPrintObject; + +namespace GUI { class RotoptimizeJob : public PlaterJob { + using FindFn = std::function; + + struct FindMethod { std::string name; FindFn findfn; }; + + static inline const FindMethod Methods[] = { + { L("Best misalignment"), sla::find_best_misalignment_rotation }, + { L("Least supports"), sla::find_best_misalignment_rotation } + }; + + size_t m_method_id = 0; + float m_accuracy = 0.75; +protected: + + void prepare() override; + public: + RotoptimizeJob(std::shared_ptr pri, Plater *plater) : PlaterJob{std::move(pri), plater} {} void process() override; void finalize() override; + + static constexpr size_t get_methods_count() { return std::size(Methods); } + static const auto & get_method_names() + { + static bool m_method_names_valid = false; + static std::array m_method_names; + + if (!m_method_names_valid) { + + for (size_t i = 0; i < std::size(Methods); ++i) + m_method_names[i] = _utf8(Methods[i].name); + + m_method_names_valid = true; + } + + return m_method_names; + } }; }} // namespace Slic3r::GUI From f21e0ae0ff7ebdadc92e063eee912da0e43f5385 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Mar 2021 10:01:50 +0100 Subject: [PATCH 030/154] Least supports optimization revived. Fix missing include on Win32 Cleanup benchmarking code --- src/libslic3r/SLA/Rotfinder.cpp | 291 +++++++++++++++++++++++-- src/libslic3r/SLA/Rotfinder.hpp | 6 +- src/slic3r/GUI/Jobs/RotoptimizeJob.hpp | 2 +- tests/sla_print/sla_print_tests.cpp | 1 + 4 files changed, 281 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index eb54b02dc26..c97cf50107e 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -11,16 +11,16 @@ #include "libslic3r/PrintConfig.hpp" #include -#include "Model.hpp" #include -#include - namespace Slic3r { namespace sla { namespace { +inline const Vec3f DOWN = {0.f, 0.f, -1.f}; +constexpr double POINTS_PER_UNIT_AREA = 1.f; + // Get the vertices of a triangle directly in an array of 3 points std::array get_triangle_vertices(const TriangleMesh &mesh, size_t faceidx) @@ -54,11 +54,11 @@ T sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) size_t grainsize = facecount / Nthreads; size_t from = 0, to = facecount; - return execution::reduce(ex_seq, from, to, initv, mergefn, accessfn, grainsize); + return execution::reduce(ex_tbb, from, to, initv, mergefn, accessfn, grainsize); } // Try to guess the number of support points needed to support a mesh -double get_model_supportedness(const TriangleMesh &mesh, const Transform3f &tr) +double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr) { if (mesh.its.vertices.empty()) return std::nan(""); @@ -78,6 +78,100 @@ double get_model_supportedness(const TriangleMesh &mesh, const Transform3f &tr) return S / facecount; } +// Get area and normal of a triangle +struct Facestats { + Vec3f normal; + double area; + + explicit Facestats(const std::array &triangle) + { + Vec3f U = triangle[1] - triangle[0]; + Vec3f V = triangle[2] - triangle[0]; + Vec3f C = U.cross(V); + normal = C.normalized(); + area = 0.5 * C.norm(); + } +}; + +// The score function for a particular face +inline double get_supportedness_score(const Facestats &fc) +{ + // Simply get the angle (acos of dot product) between the face normal and + // the DOWN vector. + float phi = 1. - std::acos(fc.normal.dot(DOWN)) / float(PI); + + // Only consider faces that have have slopes below 90 deg: + phi = phi * (phi > 0.5); + + // Make the huge slopes more significant than the smaller slopes + phi = phi * phi * phi; + + // Multiply with the area of the current face + return fc.area * POINTS_PER_UNIT_AREA * phi; +} + +// Try to guess the number of support points needed to support a mesh +double get_supportedness_score(const TriangleMesh &mesh, const Transform3f &tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + auto accessfn = [&mesh, &tr](size_t fi) { + Facestats fc{get_transformed_triangle(mesh, tr, fi)}; + + return get_supportedness_score(fc); + }; + + size_t facecount = mesh.its.indices.size(); + size_t Nthreads = std::thread::hardware_concurrency(); + double S = unscaled(sum_score(accessfn, facecount, Nthreads)); + + return S / facecount; +} + +// Find transformed mesh ground level without copy and with parallel reduce. +float find_ground_level(const TriangleMesh &mesh, + const Transform3f & tr, + size_t threads) +{ + size_t vsize = mesh.its.vertices.size(); + + auto minfn = [](float a, float b) { return std::min(a, b); }; + + auto accessfn = [&mesh, &tr] (size_t vi) { + return (tr * mesh.its.vertices[vi]).z(); + }; + + auto zmin = std::numeric_limits::max(); + size_t granularity = vsize / threads; + return execution::reduce(ex_tbb, size_t(0), vsize, zmin, minfn, accessfn, granularity); +} + +float get_supportedness_onfloor_score(const TriangleMesh &mesh, + const Transform3f & tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + size_t Nthreads = std::thread::hardware_concurrency(); + + float zmin = find_ground_level(mesh, tr, Nthreads); + float zlvl = zmin + 0.1f; // Set up a slight tolerance from z level + + auto accessfn = [&mesh, &tr, zlvl](size_t fi) { + std::array tri = get_transformed_triangle(mesh, tr, fi); + Facestats fc{tri}; + + if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl) + return -fc.area * POINTS_PER_UNIT_AREA; + + return get_supportedness_score(fc); + }; + + size_t facecount = mesh.its.indices.size(); + double S = unscaled(sum_score(accessfn, facecount, Nthreads)); + + return S / facecount; +} + using XYRotation = std::array; // prepare the rotation transformation @@ -90,13 +184,107 @@ Transform3f to_transform3f(const XYRotation &rot) return rt; } +XYRotation from_transform3f(const Transform3f &tr) +{ + Vec3d rot3 = Geometry::Transformation{tr.cast()}.get_rotation(); + return {rot3.x(), rot3.y()}; +} + +inline bool is_on_floor(const SLAPrintObject &mo) +{ + auto opt_elevation = mo.config().support_object_elevation.getFloat(); + auto opt_padaround = mo.config().pad_around_object.getBool(); + + return opt_elevation < EPSILON || opt_padaround; +} + +// collect the rotations for each face of the convex hull +std::vector get_chull_rotations(const TriangleMesh &mesh, size_t max_count) +{ + TriangleMesh chull = mesh.convex_hull_3d(); + chull.require_shared_vertices(); + double chull2d_area = chull.convex_hull().area(); + double area_threshold = chull2d_area / (scaled(1e3) * scaled(1.)); + + size_t facecount = chull.its.indices.size(); + + struct RotArea { XYRotation rot; double area; }; + + auto inputs = reserve_vector(facecount); + + auto rotcmp = [](const RotArea &r1, const RotArea &r2) { + double xdiff = r1.rot[X] - r2.rot[X], ydiff = r1.rot[Y] - r2.rot[Y]; + return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.; + }; + + auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) { + double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y]; + return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON; + }; + + for (size_t fi = 0; fi < facecount; ++fi) { + Facestats fc{get_triangle_vertices(chull, fi)}; + + if (fc.area > area_threshold) { + auto q = Eigen::Quaternionf{}.FromTwoVectors(fc.normal, DOWN); + XYRotation rot = from_transform3f(Transform3f::Identity() * q); + RotArea ra = {rot, fc.area}; + + auto it = std::lower_bound(inputs.begin(), inputs.end(), ra, rotcmp); + + if (it == inputs.end() || !eqcmp(it->rot, rot)) + inputs.insert(it, ra); + } + } + + inputs.shrink_to_fit(); + if (!max_count) max_count = inputs.size(); + std::sort(inputs.begin(), inputs.end(), + [](const RotArea &ra, const RotArea &rb) { + return ra.area > rb.area; + }); + + auto ret = reserve_vector(std::min(max_count, inputs.size())); + for (const RotArea &ra : inputs) ret.emplace_back(ra.rot); + + return ret; +} + +// Find the best score from a set of function inputs. Evaluate for every point. +template +std::array find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn) +{ + std::array ret = {}; + + double score = std::numeric_limits::max(); + + size_t Nthreads = std::thread::hardware_concurrency(); + size_t dist = std::distance(from, to); + std::vector scores(dist, score); + + execution::for_each( + ex_tbb, size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) { + if (stopfn()) return; + + scores[i] = fn(*(from + i)); + }, + dist / Nthreads); + + auto it = std::min_element(scores.begin(), scores.end()); + + if (it != scores.end()) + ret = *(from + std::distance(scores.begin(), it)); + + return ret; +} + } // namespace -Vec2d find_best_misalignment_rotation(const SLAPrintObject & po, - float accuracy, - std::function statuscb) +Vec2d find_best_misalignment_rotation(const SLAPrintObject & po, + float accuracy, + RotOptimizeStatusCB statuscb) { - static const unsigned MAX_TRIES = 1000; + static constexpr unsigned MAX_TRIES = 1000; // return value XYRotation rot; @@ -136,22 +324,91 @@ Vec2d find_best_misalignment_rotation(const SLAPrintObject & po, // We can specify the bounds for a dimension in the following way: auto bounds = opt::bounds({ {-PI/2, PI/2}, {-PI/2, PI/2} }); - Benchmark bench; - - bench.start(); auto result = solver.to_max().optimize( [&mesh, &statusfn] (const XYRotation &rot) { statusfn(); - return get_model_supportedness(mesh, to_transform3f(rot)); + return get_misalginment_score(mesh, to_transform3f(rot)); }, opt::initvals({0., 0.}), bounds); - bench.stop(); rot = result.optimum; - std::cout << "Optimum score: " << result.score << std::endl; - std::cout << "Optimum rotation: " << result.optimum[0] << " " << result.optimum[1] << std::endl; - std::cout << "Optimization took: " << bench.getElapsedSec() << " seconds" << std::endl; + return {rot[0], rot[1]}; +} + +Vec2d find_least_supports_rotation(const SLAPrintObject & po, + float accuracy, + RotOptimizeStatusCB statuscb) +{ + static const unsigned MAX_TRIES = 1000; + + // return value + XYRotation rot; + + // We will use only one instance of this converted mesh to examine different + // rotations + TriangleMesh mesh = po.model_object()->raw_mesh(); + mesh.require_shared_vertices(); + + // To keep track of the number of iterations + unsigned status = 0; + + // The maximum number of iterations + auto max_tries = unsigned(accuracy * MAX_TRIES); + + // call status callback with zero, because we are at the start + statuscb(status); + + auto statusfn = [&statuscb, &status, &max_tries] { + // report status + statuscb(unsigned(++status * 100.0/max_tries) ); + }; + + auto stopcond = [&statuscb] { + return ! statuscb(-1); + }; + + // Different search methods have to be used depending on the model elevation + if (is_on_floor(po)) { + + std::vector inputs = get_chull_rotations(mesh, max_tries); + max_tries = inputs.size(); + + // If the model can be placed on the bed directly, we only need to + // check the 3D convex hull face rotations. + + auto objfn = [&mesh, &statusfn](const XYRotation &rot) { + statusfn(); + Transform3f tr = to_transform3f(rot); + return get_supportedness_onfloor_score(mesh, tr); + }; + + rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond); + + } else { + + // Preparing the optimizer. + size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls + opt::Optimizer solver(opt::StopCriteria{} + .max_iterations(max_tries) + .stop_condition(stopcond), + gridsize); + + // We are searching rotations around only two axes x, y. Thus the + // problem becomes a 2 dimensional optimization task. + // We can specify the bounds for a dimension in the following way: + auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} }); + + auto result = solver.to_min().optimize( + [&mesh, &statusfn] (const XYRotation &rot) + { + statusfn(); + return get_supportedness_score(mesh, to_transform3f(rot)); + }, opt::initvals({0., 0.}), bounds); + + // Save the result + rot = result.optimum; + } return {rot[0], rot[1]}; } diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 56884565fa8..c0007944af6 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -37,7 +37,11 @@ Vec2d find_best_misalignment_rotation( RotOptimizeStatusCB statuscb = [] (int) { return true; } ); - +Vec2d find_least_supports_rotation( + const SLAPrintObject& modelobj, + float accuracy = 1.0f, + RotOptimizeStatusCB statuscb = [] (int) { return true; } + ); } // namespace sla } // namespace Slic3r diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index 35703644043..dfec2d6a6b5 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -21,7 +21,7 @@ class RotoptimizeJob : public PlaterJob static inline const FindMethod Methods[] = { { L("Best misalignment"), sla::find_best_misalignment_rotation }, - { L("Least supports"), sla::find_best_misalignment_rotation } + { L("Least supports"), sla::find_least_supports_rotation } }; size_t m_method_id = 0; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 59c841468d8..1f98463cc31 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "sla_test_utils.hpp" From cb3be22de1e9a543d89ae16d2595fc7ae5ba51d6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Mar 2021 18:37:07 +0100 Subject: [PATCH 031/154] Remove leftover debug message --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b44bc01f03d..a60c74f3026 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -455,7 +455,6 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil m_gizmos[i].set_group_id(i); } - std::cout << "Load rotopt state" << std::endl; load_rotoptimize_state(); } From c8c9a4cf8b1916e92bbde7cb97ed561a21fd3df3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Mar 2021 18:45:55 +0100 Subject: [PATCH 032/154] Tolerate corrupted appconfig settings for auto rotation --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index a60c74f3026..b169327462f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -217,11 +217,18 @@ void GLGizmoRotate3D::load_rotoptimize_state() std::string method_str = wxGetApp().app_config->get("rotoptimize", "method_id"); - if (!accuracy_str.empty()) - m_rotoptimizewin_state.accuracy = std::stof(accuracy_str); + if (!accuracy_str.empty()) { + float accuracy = std::stof(accuracy_str); + accuracy = std::max(0.f, std::min(accuracy, 1.f)); - if (!method_str.empty()) - m_rotoptimizewin_state.method_id = std::stoi(method_str); + m_rotoptimizewin_state.accuracy = accuracy; + } + + if (!method_str.empty()) { + int method_id = std::stoi(method_str); + if (method_id < int(RotoptimizeJob::get_methods_count())) + m_rotoptimizewin_state.method_id = method_id; + } } void GLGizmoRotate::render_circle() const From 785c7e97ec3e3472ba1270d8c669f8c10f8e710a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Mar 2021 18:48:10 +0100 Subject: [PATCH 033/154] Change configuration bank name for SLA auto rotation --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 8 ++++---- src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b169327462f..19780079763 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -212,10 +212,10 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi void GLGizmoRotate3D::load_rotoptimize_state() { std::string accuracy_str = - wxGetApp().app_config->get("rotoptimize", "accuracy"); + wxGetApp().app_config->get("sla_auto_rotate", "accuracy"); std::string method_str = - wxGetApp().app_config->get("rotoptimize", "method_id"); + wxGetApp().app_config->get("sla_auto_rotate", "method_id"); if (!accuracy_str.empty()) { float accuracy = std::stof(accuracy_str); @@ -550,13 +550,13 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, int citem = state.method_id; if (ImGui::Combo(_L("Choose method").c_str(), &citem, options, methods_cnt) ) { state.method_id = citem; - wxGetApp().app_config->set("rotoptimize", "method_id", std::to_string(state.method_id)); + wxGetApp().app_config->set("sla_auto_rotate", "method_id", std::to_string(state.method_id)); } float accuracy = state.accuracy; if (imgui->slider_float(_L("Accuracy/Speed"), &accuracy, 0.01f, 1.f, "%.1f")) { state.accuracy = accuracy; - wxGetApp().app_config->set("rotoptimize", "accuracy", std::to_string(state.accuracy)); + wxGetApp().app_config->set("sla_auto_rotate", "accuracy", std::to_string(state.accuracy)); } ImGui::Separator(); diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 2e83bbf962c..05b141131cb 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -16,10 +16,10 @@ namespace Slic3r { namespace GUI { void RotoptimizeJob::prepare() { std::string accuracy_str = - wxGetApp().app_config->get("rotoptimize", "accuracy"); + wxGetApp().app_config->get("sla_auto_rotate", "accuracy"); std::string method_str = - wxGetApp().app_config->get("rotoptimize", "method_id"); + wxGetApp().app_config->get("sla_auto_rotate", "method_id"); if (!accuracy_str.empty()) m_accuracy = std::stof(accuracy_str); From b0d2df95e476900439c23386d14d939dcee25770 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 26 Mar 2021 18:20:31 +0100 Subject: [PATCH 034/154] Increase performance of "best misalignment" method --- src/libslic3r/SLA/Rotfinder.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index c97cf50107e..89de1cf83d5 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -63,12 +63,14 @@ double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr) if (mesh.its.vertices.empty()) return std::nan(""); auto accessfn = [&mesh, &tr](size_t fi) { - Vec3f n = normal(get_transformed_triangle(mesh, tr, fi)); + auto triangle = get_transformed_triangle(mesh, tr, fi); + Vec3f U = triangle[1] - triangle[0]; + Vec3f V = triangle[2] - triangle[0]; + Vec3f C = U.cross(V); // We should score against the alignment with the reference planes - return scaled(std::abs(n.dot(Vec3f::UnitX())) + - std::abs(n.dot(Vec3f::UnitY())) + - std::abs(n.dot(Vec3f::UnitZ()))); + return scaled(std::abs(C.dot(Vec3f::UnitX())) + + std::abs(C.dot(Vec3f::UnitY()))); }; size_t facecount = mesh.its.indices.size(); From 6f04f1b1d928121df50ea4090091f199567e9146 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 26 Mar 2021 18:22:53 +0100 Subject: [PATCH 035/154] Allow auto-rotation of objects not completely inside bed. Don't use SLAPrintObject as the input for optimization. Use ModelObject and pass the print config to the optimization in RotoptimizeJob::prepare() --- src/libslic3r/SLA/Rotfinder.cpp | 37 ++++++++++++++--------- src/libslic3r/SLA/Rotfinder.hpp | 42 ++++++++++++++++++++------ src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 24 ++++++++++----- src/slic3r/GUI/Jobs/RotoptimizeJob.hpp | 11 ++++--- 4 files changed, 77 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 89de1cf83d5..6e8a0ce6b64 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "libslic3r/SLAPrint.hpp" #include "libslic3r/PrintConfig.hpp" @@ -192,10 +193,10 @@ XYRotation from_transform3f(const Transform3f &tr) return {rot3.x(), rot3.y()}; } -inline bool is_on_floor(const SLAPrintObject &mo) +inline bool is_on_floor(const SLAPrintObjectConfig &cfg) { - auto opt_elevation = mo.config().support_object_elevation.getFloat(); - auto opt_padaround = mo.config().pad_around_object.getBool(); + auto opt_elevation = cfg.support_object_elevation.getFloat(); + auto opt_padaround = cfg.pad_around_object.getBool(); return opt_elevation < EPSILON || opt_padaround; } @@ -282,9 +283,8 @@ std::array find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn) } // namespace -Vec2d find_best_misalignment_rotation(const SLAPrintObject & po, - float accuracy, - RotOptimizeStatusCB statuscb) +Vec2d find_best_misalignment_rotation(const ModelObject & mo, + const RotOptimizeParams ¶ms) { static constexpr unsigned MAX_TRIES = 1000; @@ -293,14 +293,16 @@ Vec2d find_best_misalignment_rotation(const SLAPrintObject & po, // We will use only one instance of this converted mesh to examine different // rotations - TriangleMesh mesh = po.model_object()->raw_mesh(); + TriangleMesh mesh = mo.raw_mesh(); mesh.require_shared_vertices(); // To keep track of the number of iterations int status = 0; // The maximum number of iterations - auto max_tries = unsigned(accuracy * MAX_TRIES); + auto max_tries = unsigned(params.accuracy() * MAX_TRIES); + + auto &statuscb = params.statuscb(); // call status callback with zero, because we are at the start statuscb(status); @@ -338,9 +340,8 @@ Vec2d find_best_misalignment_rotation(const SLAPrintObject & po, return {rot[0], rot[1]}; } -Vec2d find_least_supports_rotation(const SLAPrintObject & po, - float accuracy, - RotOptimizeStatusCB statuscb) +Vec2d find_least_supports_rotation(const ModelObject & mo, + const RotOptimizeParams ¶ms) { static const unsigned MAX_TRIES = 1000; @@ -349,14 +350,16 @@ Vec2d find_least_supports_rotation(const SLAPrintObject & po, // We will use only one instance of this converted mesh to examine different // rotations - TriangleMesh mesh = po.model_object()->raw_mesh(); + TriangleMesh mesh = mo.raw_mesh(); mesh.require_shared_vertices(); // To keep track of the number of iterations unsigned status = 0; // The maximum number of iterations - auto max_tries = unsigned(accuracy * MAX_TRIES); + auto max_tries = unsigned(params.accuracy() * MAX_TRIES); + + auto &statuscb = params.statuscb(); // call status callback with zero, because we are at the start statuscb(status); @@ -370,8 +373,14 @@ Vec2d find_least_supports_rotation(const SLAPrintObject & po, return ! statuscb(-1); }; + SLAPrintObjectConfig pocfg; + if (params.print_config()) + pocfg.apply(*params.print_config(), true); + + pocfg.apply(mo.config.get()); + // Different search methods have to be used depending on the model elevation - if (is_on_floor(po)) { + if (is_on_floor(pocfg)) { std::vector inputs = get_chull_rotations(mesh, max_tries); max_tries = inputs.size(); diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index c0007944af6..77a39016db6 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -8,13 +8,39 @@ namespace Slic3r { +class ModelObject; class SLAPrintObject; class TriangleMesh; +class DynamicPrintConfig; namespace sla { using RotOptimizeStatusCB = std::function; +class RotOptimizeParams { + float m_accuracy = 1.; + const DynamicPrintConfig *m_print_config = nullptr; + RotOptimizeStatusCB m_statuscb = [](int) { return true; }; + +public: + + RotOptimizeParams &accuracy(float a) { m_accuracy = a; return *this; } + RotOptimizeParams &print_config(const DynamicPrintConfig *c) + { + m_print_config = c; + return *this; + } + RotOptimizeParams &statucb(RotOptimizeStatusCB cb) + { + m_statuscb = std::move(cb); + return *this; + } + + float accuracy() const { return m_accuracy; } + const DynamicPrintConfig * print_config() const { return m_print_config; } + const RotOptimizeStatusCB &statuscb() const { return m_statuscb; } +}; + /** * The function should find the best rotation for SLA upside down printing. * @@ -31,17 +57,13 @@ using RotOptimizeStatusCB = std::function; * * @return Returns the rotations around each axis (x, y, z) */ -Vec2d find_best_misalignment_rotation( - const SLAPrintObject& modelobj, - float accuracy = 1.0f, - RotOptimizeStatusCB statuscb = [] (int) { return true; } - ); +Vec2d find_best_misalignment_rotation(const ModelObject &modelobj, + const RotOptimizeParams & = {}); -Vec2d find_least_supports_rotation( - const SLAPrintObject& modelobj, - float accuracy = 1.0f, - RotOptimizeStatusCB statuscb = [] (int) { return true; } - ); +Vec2d find_least_supports_rotation(const ModelObject &modelobj, + const RotOptimizeParams & = {}); + +double find_Z_fit_to_bed_rotation(const ModelObject &mo, const BoundingBox &bed); } // namespace sla } // namespace Slic3r diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 05b141131cb..04144112e8c 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -7,6 +7,7 @@ #include "libslic3r/SLAPrint.hpp" #include "slic3r/GUI/Plater.hpp" +#include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "libslic3r/AppConfig.hpp" @@ -29,25 +30,32 @@ void RotoptimizeJob::prepare() m_accuracy = std::max(0.f, std::min(m_accuracy, 1.f)); m_method_id = std::max(size_t(0), std::min(get_methods_count() - 1, m_method_id)); + + m_default_print_cfg = wxGetApp().preset_bundle->full_config(); } void RotoptimizeJob::process() { int obj_idx = m_plater->get_selected_object_idx(); - if (obj_idx < 0 || int(m_plater->sla_print().objects().size()) <= obj_idx) + if (obj_idx < 0) return; ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; - const SLAPrintObject *po = m_plater->sla_print().objects()[size_t(obj_idx)]; - if (!o || !po) return; + if (!o) return; - Vec2d r = Methods[m_method_id].findfn(*po, m_accuracy, [this](int s) { - if (s > 0 && s < 100) - update_status(s, _(L("Searching for optimal orientation"))); + auto params = + sla::RotOptimizeParams{} + .accuracy(m_accuracy) + .print_config(&m_default_print_cfg) + .statucb([this](int s) { + if (s > 0 && s < 100) + update_status(s, _(L("Searching for optimal orientation"))); - return !was_canceled(); - }); + return !was_canceled(); + }); + + Vec2d r = Methods[m_method_id].findfn(*o, params); double mindist = 6.0; // FIXME diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index dfec2d6a6b5..cb9a96b56b1 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -4,18 +4,16 @@ #include "PlaterJob.hpp" #include "libslic3r/SLA/Rotfinder.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { -class SLAPrintObject; - namespace GUI { class RotoptimizeJob : public PlaterJob { - using FindFn = std::function; + using FindFn = std::function; struct FindMethod { std::string name; FindFn findfn; }; @@ -26,6 +24,9 @@ class RotoptimizeJob : public PlaterJob size_t m_method_id = 0; float m_accuracy = 0.75; + + DynamicPrintConfig m_default_print_cfg; + protected: void prepare() override; From d74c616905c36e14009272316266de3dee45044b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 31 Mar 2021 14:49:22 +0200 Subject: [PATCH 036/154] Remove accuracy slicer No practical use --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 19780079763..b22bc080e00 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -553,12 +553,6 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, wxGetApp().app_config->set("sla_auto_rotate", "method_id", std::to_string(state.method_id)); } - float accuracy = state.accuracy; - if (imgui->slider_float(_L("Accuracy/Speed"), &accuracy, 0.01f, 1.f, "%.1f")) { - state.accuracy = accuracy; - wxGetApp().app_config->set("sla_auto_rotate", "accuracy", std::to_string(state.accuracy)); - } - ImGui::Separator(); if ( imgui->button(_L("Optimize")) ) { From a2be47a6e63c59dcbe9665bf3ff52626a2d6a2ec Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 31 Mar 2021 14:50:24 +0200 Subject: [PATCH 037/154] Allow rotation of multiple selected items. Disable auto positioning --- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 30 +++++++++---- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 11 ++++- src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 61 ++++++++++++++++---------- src/slic3r/GUI/Jobs/RotoptimizeJob.hpp | 13 +++++- src/slic3r/GUI/Plater.cpp | 26 ++++++----- src/slic3r/GUI/Plater.hpp | 2 +- 6 files changed, 99 insertions(+), 44 deletions(-) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 3f1207b4799..391c1fe2846 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -78,7 +78,7 @@ void ArrangeJob::prepare_all() { for (ModelObject *obj: m_plater->model().objects) for (ModelInstance *mi : obj->instances) { ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; - cont.emplace_back(get_arrange_poly(PtrWrapper{mi}, m_plater)); + cont.emplace_back(get_arrange_poly(mi, m_plater)); } if (auto wti = get_wipe_tower_arrangepoly(*m_plater)) @@ -111,7 +111,7 @@ void ArrangeJob::prepare_selected() { for (size_t i = 0; i < inst_sel.size(); ++i) { ArrangePolygon &&ap = - get_arrange_poly(PtrWrapper{mo->instances[i]}, m_plater); + get_arrange_poly(mo->instances[i], m_plater); ArrangePolygons &cont = mo->instances[i]->printable ? (inst_sel[i] ? m_selected : @@ -161,12 +161,7 @@ void ArrangeJob::process() { static const auto arrangestr = _(L("Arranging")); - const GLCanvas3D::ArrangeSettings &settings = - static_cast(m_plater->canvas3D())->get_arrange_settings(); - - arrangement::ArrangeParams params; - params.allow_rotations = settings.enable_rotation; - params.min_obj_distance = scaled(settings.distance); + arrangement::ArrangeParams params = get_arrange_params(m_plater); auto count = unsigned(m_selected.size() + m_unprintable.size()); Points bedpts = get_bed_shape(*m_plater->config()); @@ -235,4 +230,23 @@ double bed_stride(const Plater *plater) { return scaled((1. + LOGICAL_BED_GAP) * bedwidth); } +template<> +arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, + const Plater * plater) +{ + return get_arrange_poly(PtrWrapper{inst}, plater); +} + +arrangement::ArrangeParams get_arrange_params(Plater *p) +{ + const GLCanvas3D::ArrangeSettings &settings = + static_cast(p->canvas3D())->get_arrange_settings(); + + arrangement::ArrangeParams params; + params.allow_rotations = settings.enable_rotation; + params.min_obj_distance = scaled(settings.distance); + + return params; +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 38aafb52c18..7fa4b927e61 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -4,7 +4,11 @@ #include "PlaterJob.hpp" #include "libslic3r/Arrange.hpp" -namespace Slic3r { namespace GUI { +namespace Slic3r { + +class ModelInstance; + +namespace GUI { class ArrangeJob : public PlaterJob { @@ -89,6 +93,11 @@ arrangement::ArrangePolygon get_arrange_poly(T obj, const Plater *plater) return ap; } +template<> +arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, + const Plater * plater); + +arrangement::ArrangeParams get_arrange_params(Plater *p); }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 04144112e8c..a670affefb9 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -32,36 +32,59 @@ void RotoptimizeJob::prepare() m_method_id = std::max(size_t(0), std::min(get_methods_count() - 1, m_method_id)); m_default_print_cfg = wxGetApp().preset_bundle->full_config(); + + const auto &sel = m_plater->get_selection().get_content(); + + m_selected_object_ids.clear(); + m_selected_object_ids.reserve(sel.size()); + for (auto &[obj_idx, ignore] : sel) + m_selected_object_ids.emplace_back(obj_idx); } void RotoptimizeJob::process() { - int obj_idx = m_plater->get_selected_object_idx(); - if (obj_idx < 0) - return; - - ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; - - if (!o) return; - + int prev_status = 0; auto params = sla::RotOptimizeParams{} .accuracy(m_accuracy) .print_config(&m_default_print_cfg) - .statucb([this](int s) { + .statucb([this, &prev_status](int s) + { if (s > 0 && s < 100) - update_status(s, _(L("Searching for optimal orientation"))); + update_status(prev_status + s / m_selected_object_ids.size(), + _(L("Searching for optimal orientation"))); return !was_canceled(); }); - Vec2d r = Methods[m_method_id].findfn(*o, params); - double mindist = 6.0; // FIXME + for (ObjRot &objrot : m_selected_object_ids) { + ModelObject *o = m_plater->model().objects[size_t(objrot.idx)]; + if (!o) continue; + + if (Methods[m_method_id].findfn) + objrot.rot = Methods[m_method_id].findfn(*o, params); + + prev_status += 100 / m_selected_object_ids.size(); + + if (was_canceled()) break; + } + + update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : + _(L("Orientation found."))); +} + +void RotoptimizeJob::finalize() +{ + if (was_canceled()) return; + + for (const ObjRot &objrot : m_selected_object_ids) { + ModelObject *o = m_plater->model().objects[size_t(objrot.idx)]; + if (!o) continue; - if (!was_canceled()) { for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], 0.}); + if (objrot.rot) + oi->set_rotation({objrot.rot->x(), objrot.rot->y(), 0.}); auto trmatrix = oi->get_transformation().get_matrix(); Polygon trchull = o->convex_hull_2d(trmatrix); @@ -77,19 +100,13 @@ void RotoptimizeJob::process() oi->set_rotation(rt); } - m_plater->find_new_position(o->instances, scaled(mindist)); - // Correct the z offset of the object which was corrupted be // the rotation o->ensure_on_bed(); + +// m_plater->find_new_position(o->instances); } - update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : - _(L("Orientation found."))); -} - -void RotoptimizeJob::finalize() -{ if (!was_canceled()) m_plater->update(); diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index cb9a96b56b1..1535d0fa6bc 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -19,7 +19,9 @@ class RotoptimizeJob : public PlaterJob static inline const FindMethod Methods[] = { { L("Best misalignment"), sla::find_best_misalignment_rotation }, - { L("Least supports"), sla::find_least_supports_rotation } + { L("Least supports"), sla::find_least_supports_rotation }, + // Just a min area bounding box that is done for all methods anyway. + { L("Z axis only"), nullptr } }; size_t m_method_id = 0; @@ -27,6 +29,15 @@ class RotoptimizeJob : public PlaterJob DynamicPrintConfig m_default_print_cfg; + struct ObjRot + { + size_t idx; + std::optional rot; + ObjRot(size_t id): idx{id}, rot{} {} + }; + + std::vector m_selected_object_ids; + protected: void prepare() override; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a640d6e280e..e0a5031c230 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2669,32 +2669,36 @@ void Plater::priv::mirror(Axis axis) view3D->mirror_selection(axis); } -void Plater::find_new_position(const ModelInstancePtrs &instances, - coord_t min_d) +void Plater::find_new_position(const ModelInstancePtrs &instances) { arrangement::ArrangePolygons movable, fixed; + arrangement::ArrangeParams arr_params = get_arrange_params(this); for (const ModelObject *mo : p->model.objects) - for (const ModelInstance *inst : mo->instances) { + for (ModelInstance *inst : mo->instances) { auto it = std::find(instances.begin(), instances.end(), inst); - auto arrpoly = inst->get_arrange_polygon(); + auto arrpoly = get_arrange_poly(inst, this); if (it == instances.end()) fixed.emplace_back(std::move(arrpoly)); - else + else { + arrpoly.setter = [it](const arrangement::ArrangePolygon &p) { + if (p.is_arranged() && p.bed_idx == 0) { + Vec2d t = p.translation.cast(); + (*it)->apply_arrange_result(t, p.rotation); + } + }; movable.emplace_back(std::move(arrpoly)); + } } if (auto wt = get_wipe_tower_arrangepoly(*this)) fixed.emplace_back(*wt); - arrangement::arrange(movable, fixed, get_bed_shape(*config()), - arrangement::ArrangeParams{min_d}); + arrangement::arrange(movable, fixed, get_bed_shape(*config()), arr_params); - for (size_t i = 0; i < instances.size(); ++i) - if (movable[i].bed_idx == 0) - instances[i]->apply_arrange_result(movable[i].translation.cast(), - movable[i].rotation); + for (auto & m : movable) + m.apply(); } void Plater::priv::split_object() diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ff81dad26e7..16590ed9b1a 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -258,7 +258,7 @@ public: BoundingBoxf bed_shape_bb() const; void arrange(); - void find_new_position(const ModelInstancePtrs &instances, coord_t min_d); + void find_new_position(const ModelInstancePtrs &instances); void set_current_canvas_as_dirty(); void unbind_canvas_event_handlers(); From 389a3d0a6a990a538a7f2f22a8f2eaff752705c6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 31 Mar 2021 15:21:22 +0200 Subject: [PATCH 038/154] Better naming of gui controls --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 2 +- src/slic3r/GUI/Jobs/RotoptimizeJob.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b22bc080e00..6b6905e4d28 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -548,7 +548,7 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, } int citem = state.method_id; - if (ImGui::Combo(_L("Choose method").c_str(), &citem, options, methods_cnt) ) { + if (ImGui::Combo(_L("Choose goal").c_str(), &citem, options, methods_cnt) ) { state.method_id = citem; wxGetApp().app_config->set("sla_auto_rotate", "method_id", std::to_string(state.method_id)); } diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index 1535d0fa6bc..3144f3c3e64 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -18,7 +18,7 @@ class RotoptimizeJob : public PlaterJob struct FindMethod { std::string name; FindFn findfn; }; static inline const FindMethod Methods[] = { - { L("Best misalignment"), sla::find_best_misalignment_rotation }, + { L("Best surface quality"), sla::find_best_misalignment_rotation }, { L("Least supports"), sla::find_least_supports_rotation }, // Just a min area bounding box that is done for all methods anyway. { L("Z axis only"), nullptr } From 27229c0b5f9b2b8ed08b95211a210d6ac8087d60 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 31 Mar 2021 16:04:16 +0200 Subject: [PATCH 039/154] Small improvement to "least supports" method --- src/libslic3r/SLA/Rotfinder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 6e8a0ce6b64..b8492127935 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -103,8 +103,8 @@ inline double get_supportedness_score(const Facestats &fc) // the DOWN vector. float phi = 1. - std::acos(fc.normal.dot(DOWN)) / float(PI); - // Only consider faces that have have slopes below 90 deg: - phi = phi * (phi > 0.5); + // Only consider faces that have slopes below 90 deg: + phi = phi * (phi >= 0.5f); // Make the huge slopes more significant than the smaller slopes phi = phi * phi * phi; From bb9d1b367942e1d2ab1b3d1b11df361ddbb5dff6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 31 Mar 2021 16:31:53 +0200 Subject: [PATCH 040/154] Remove right click menu item for "optimize orientation" --- src/slic3r/GUI/GUI_Factories.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 0bd523b2615..01ea53ad372 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -858,10 +858,6 @@ void MenuFactory::create_sla_object_menu() []() { return plater()->can_split(true); }, m_parent); m_sla_object_menu.AppendSeparator(); - - // Add the automatic rotation sub-menu - append_menu_item(&m_sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."), - [](wxCommandEvent&) { plater()->optimize_rotation(); }); } void MenuFactory::create_part_menu() From ad49a3c84de6c08ec314a40b003bdc6d84e3cd06 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Wed, 7 Apr 2021 13:04:29 +0200 Subject: [PATCH 041/154] TriLAB update 0.0.7 https://github.com/prusa3d/PrusaSlicer-settings/pull/127 --- resources/profiles/TriLAB.idx | 3 +- resources/profiles/TriLAB.ini | 88 ++++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/resources/profiles/TriLAB.idx b/resources/profiles/TriLAB.idx index 4a097ed6aed..65f47d59a66 100644 --- a/resources/profiles/TriLAB.idx +++ b/resources/profiles/TriLAB.idx @@ -1,8 +1,9 @@ min_slic3r_version = 2.3.0-alpha3 +0.0.7 Added PLA, PETG profiles for 0.25 nozzle, fixed supports on 0.8 nozzle profile, fixed max volumetric speed, disabled elefant foot compensation 0.0.6 Added material TPU 93A (SMARTFIL) 0.0.5 Removed obsolete host keys. 0.0.4 Added PLA, PETG profiles for 0.8 nozzle, update print materials 0.0.3 Added DeltiQ 2, DeltiQ 2 Plus printers, 0.10mm, 0.20mm FLEX print profiles, updated print materials, flexprint extension support min_slic3r_version = 2.3.0-alpha0 0.0.2 Added 0.15mm print profile -0.0.1 Initial TriLAB bundle +0.0.1 Initial TriLAB bundle \ No newline at end of file diff --git a/resources/profiles/TriLAB.ini b/resources/profiles/TriLAB.ini index d31461510d2..d6824badeb5 100644 --- a/resources/profiles/TriLAB.ini +++ b/resources/profiles/TriLAB.ini @@ -6,7 +6,7 @@ name = TriLAB # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.6 +config_version = 0.0.7 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/TriLAB/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -17,16 +17,16 @@ config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/Prus [printer_model:DQ2] name = DeltiQ 2 -variants = 0.4; 0.8 +variants = 0.4; 0.25; 0.8 technology = FFF family = DeltiQ 2 bed_model = dq2_bed.stl bed_texture = dq2_bed_texture.svg -default_materials = DeltiQ - PLA - Generic; DeltiQ - PETG - Generic; DeltiQ - ABS - Generic; DeltiQ - PLA - ExtraFill (Fillamentum); DeltiQ - PETG (Devil Design); DeltiQ - ABS - ExtraFill (Fillamentum); DeltiQ - ASA - ExtraFill (Fillamentum); DeltiQ - CPE - HG100 (Fillamentum); DeltiQ FP2 - PLA - Generic; DeltiQ FP2 - PETG - Generic; DeltiQ FP2 - ABS - Generic; DeltiQ FP2 - PLA - ExtraFill (Fillamentum); DeltiQ FP2 - PETG (Devil Design); DeltiQ FP2 - ABS - ExtraFill (Fillamentum); DeltiQ FP2 - ASA - ExtraFill (Fillamentum); DeltiQ FP2 - CPE - HG100 (Fillamentum); DeltiQ FP2 - FLEX - Generic; DeltiQ FP2 - TPU 92A - FlexFill (Fillamentum); DeltiQ FP2 - TPU 98A - FlexFill (Fillamentum); DeltiQ FP2 - TPU 93A (SMARTFIL); DeltiQ - PLA - ExtraFill (Fillamentum) @0.8 nozzle +default_materials = DeltiQ - PLA - Generic; DeltiQ - PETG - Generic; DeltiQ - ABS - Generic; DeltiQ - PLA - ExtraFill (Fillamentum); DeltiQ - PETG (Devil Design); DeltiQ - ABS - ExtraFill (Fillamentum); DeltiQ - ASA - ExtraFill (Fillamentum); DeltiQ - CPE - HG100 (Fillamentum); DeltiQ FP2 - PLA - Generic; DeltiQ FP2 - PETG - Generic; DeltiQ FP2 - ABS - Generic; DeltiQ FP2 - PLA - ExtraFill (Fillamentum); DeltiQ FP2 - PETG (Devil Design); DeltiQ FP2 - ABS - ExtraFill (Fillamentum); DeltiQ FP2 - ASA - ExtraFill (Fillamentum); DeltiQ FP2 - CPE - HG100 (Fillamentum); DeltiQ FP2 - FLEX - Generic; DeltiQ FP2 - TPU 92A - FlexFill (Fillamentum); DeltiQ FP2 - TPU 98A - FlexFill (Fillamentum); DeltiQ FP2 - TPU 93A (SMARTFIL); DeltiQ - PLA - ExtraFill (Fillamentum) @0.8 nozzle; DeltiQ - PLA - ExtraFill (Fillamentum) @0.25 nozzle [printer_model:DQ2P] name = DeltiQ 2 Plus -variants = 0.4; 0.8 +variants = 0.4; 0.25; 0.8 technology = FFF family = DeltiQ 2 bed_model = dq2_bed.stl @@ -110,7 +110,7 @@ complete_objects = 0 default_acceleration = 2000 dont_support_bridges = 0 draft_shield = 0 -elefant_foot_compensation = 0.2 +elefant_foot_compensation = 0.0 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 @@ -150,7 +150,7 @@ min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 1 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{printer_model}_{filament_type[0]}_{layer_height}mm_{print_time}.gcode +output_filename_format = {input_filename_base}_{printer_model}_{filament_type[0]}_{layer_height}mm_{print_time}_{timestamp}.gcode overhangs = 1 perimeter_acceleration = 1500 perimeter_extruder = 1 @@ -283,6 +283,37 @@ travel_speed = 200 max_print_speed = 40 complete_objects = 1 +[print:DeltiQ 0.07mm Quality @0.25 nozzle] +inherits = DeltiQ 0.20mm Normal +bottom_solid_layers = 6 +bottom_solid_min_thickness = 0.5 +bridge_speed = 60 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and nozzle_diameter[0]==0.25 +elefant_foot_compensation = 0.0 +external_perimeter_extrusion_width = 0.27 +extrusion_width = 0.25 +first_layer_extrusion_width = 0.26 +first_layer_height = 0.1 +infill_extrusion_width = 0.27 +infill_overlap = 50% +layer_height = 0.07 +overhangs = 1 +perimeter_extrusion_width = 0.27 +perimeters = 5 +solid_infill_extrusion_width = 0.27 +skirts = 2 +support_material_extrusion_width = 0.27 +support_material_contact_distance = 0.1 +top_infill_extrusion_width = 0.27 +top_solid_layers = 6 +top_solid_min_thickness = 0.5 +thin_walls = 1 + +[print:DeltiQ 0.20mm Normal @0.25 nozzle] +inherits = DeltiQ 0.07mm Quality @0.25 nozzle +first_layer_height = 0.2 +layer_height = 0.2 + [print:DeltiQ 0.40mm Normal @0.8 nozzle] inherits = DeltiQ 0.20mm Normal bottom_solid_layers = 3 @@ -290,7 +321,7 @@ bottom_solid_min_thickness = 1.2 bridge_flow_ratio = 0.90 bridge_speed = 20 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and nozzle_diameter[0]==0.8 -elefant_foot_compensation = 0.2 +elefant_foot_compensation = 0.0 external_perimeter_extrusion_width = 0.80 external_perimeter_speed = 30 extrusion_width = 0.80 @@ -311,6 +342,8 @@ perimeters = 2 small_perimeter_speed = 20 solid_infill_extrusion_width = 0.8 solid_infill_speed = 60 +support_material_extrusion_width = 0.8 +support_material_contact_distance = 0.2 top_infill_extrusion_width = 0.8 top_solid_infill_speed = 40 top_solid_layers = 4 @@ -412,7 +445,7 @@ fan_below_layer_time = 20 filament_vendor = Fillamentum filament_cost = 774 filament_density = 1.08 -filament_max_volumetric_speed = 4 +filament_max_volumetric_speed = 8 filament_retract_before_travel = 3 filament_retract_before_wipe = 70% filament_retract_layer_change = 1 @@ -474,6 +507,19 @@ min_print_speed = 10 slowdown_below_layer_time = 5 temperature = 260 +[filament:DeltiQ - PLA - ExtraFill (Fillamentum) @0.25 nozzle] +inherits = DeltiQ - PLA - ExtraFill (Fillamentum) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and !(printer_notes=~/.*FLEXPRINT.*/) and nozzle_diameter[0]==0.25 + +[filament:DeltiQ - PETG (Devil Design) @0.25 nozzle] +inherits = DeltiQ - PETG (Devil Design) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and !(printer_notes=~/.*FLEXPRINT.*/) and nozzle_diameter[0]==0.25 +first_layer_temperature = 225 +temperature = 220 +max_fan_speed = 65 +min_fan_speed = 40 +bridge_fan_speed = 100 + [filament:DeltiQ - PLA - ExtraFill (Fillamentum) @0.8 nozzle] inherits = DeltiQ - PLA - ExtraFill (Fillamentum) compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and !(printer_notes=~/.*FLEXPRINT.*/) and nozzle_diameter[0]==0.8 @@ -536,7 +582,7 @@ filament_vendor = Generic filament_cost = 1870 filament_density = 1.22 filament_deretract_speed = nil -filament_max_volumetric_speed = 0.7 +filament_max_volumetric_speed = 3.0 filament_retract_before_travel = 2 filament_retract_before_wipe = 70% filament_retract_layer_change = 0 @@ -634,7 +680,7 @@ filament_vendor = Fillamentum filament_cost = 1870 filament_density = 1.22 filament_deretract_speed = nil -filament_max_volumetric_speed = 2.9 +filament_max_volumetric_speed = 3.0 filament_retract_before_travel = 2 filament_retract_before_wipe = 70% filament_retract_layer_change = 0 @@ -665,7 +711,7 @@ filament_vendor = Fillamentum filament_cost = 1870 filament_density = 1.22 filament_deretract_speed = nil -filament_max_volumetric_speed = 2.9 +filament_max_volumetric_speed = 3.0 filament_retract_before_travel = 2 filament_retract_before_wipe = 70% filament_retract_layer_change = 0 @@ -689,7 +735,7 @@ extrusion_multiplier = 1.10 filament_cost = 1870 filament_density = 1.23 filament_deretract_speed = nil -filament_max_volumetric_speed = 2.9 +filament_max_volumetric_speed = 3.0 filament_retract_before_wipe = 70% filament_retract_length = 2.5 filament_retract_speed = 20 @@ -822,6 +868,15 @@ printer_model = DQ2 printer_variant = 0.4 max_print_height = 320 +[printer:DeltiQ 2 - 0.25 nozzle] +inherits = DeltiQ 2 +printer_variant = 0.25 +max_layer_height = 0.15 +min_layer_height = 0.07 +nozzle_diameter = 0.25 +default_filament_profile = "DeltiQ - PLA - ExtraFill (Fillamentum) @0.25 nozzle" +default_print_profile = DeltiQ 0.07mm Quality @0.25 nozzle + [printer:DeltiQ 2 - 0.8 nozzle] inherits = DeltiQ 2 printer_variant = 0.8 @@ -837,6 +892,15 @@ printer_model = DQ2P printer_variant = 0.4 max_print_height = 500 +[printer:DeltiQ 2 Plus - 0.25 nozzle] +inherits = DeltiQ 2 Plus +printer_variant = 0.25 +max_layer_height = 0.15 +min_layer_height = 0.07 +nozzle_diameter = 0.25 +default_filament_profile = "DeltiQ - PLA - ExtraFill (Fillamentum) @0.25 nozzle" +default_print_profile = DeltiQ 0.07mm Quality @0.25 nozzle + [printer:DeltiQ 2 Plus - 0.8 nozzle] inherits = DeltiQ 2 Plus printer_variant = 0.8 From ac781963d4b16afe21a76242d5dd9e91b85fec08 Mon Sep 17 00:00:00 2001 From: MarkMan0 Date: Thu, 8 Apr 2021 13:45:37 +0200 Subject: [PATCH 042/154] Update for Inat printers + Improved start gcode + changed filename format --- resources/profiles/INAT.idx | 1 + resources/profiles/INAT.ini | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index e682f4ed234..0d81f55a0f4 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,2 +1,3 @@ min_slic3r_version = 2.4.0-alpha0 0.0.1 Initial version +0.0.2 Improved start gcode, changed filename format diff --git a/resources/profiles/INAT.ini b/resources/profiles/INAT.ini index e11aeab86fe..2d180d95476 100644 --- a/resources/profiles/INAT.ini +++ b/resources/profiles/INAT.ini @@ -3,7 +3,7 @@ [vendor] # Vendor name will be shown by the Config Wizard. name = INAT -config_version = 0.0.1 +config_version = 0.0.2 config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/INAT/ ### @@ -133,7 +133,7 @@ extruder_clearance_radius = 85 extruder_clearance_height = 34 gcode_comments = 0 gcode_label_objects = 0 -output_filename_format = {input_filename_base}_{filament_type[0]}_{print_time}.gcode +output_filename_format = {input_filename_base}_{filament_type[0]}.gcode [print:0.2mm Standard @PROTON_X] @@ -193,7 +193,7 @@ use_firmware_retraction = 0 use_volumetric_e = 0 variable_layer_height = 1 #gcodes -start_gcode = G28 ;Home\nG0 Z0.3 F200 ;Move nozzle down\nM192 S50 ; Wait for probe temperature to settle\nG29\nG0 X0 Y0 Z30 F6000\nM84 E\nM0\nG1 Z15.0 F6000 ;Move the platform down 15mm\n;Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0\n +start_gcode = G28 ;Home\nG0 X-2 Y150 F6000 ;Move to the side\nG0 Z0.3 F200 ;Move nozzle down\nM192 ; Wait for probe temperature to settle\nG28 Z\nG29\nG0 X0 Y0 Z30 F6000\nM84 E\nM0\nG1 Z15.0 F6000 ;Move the platform down 15mm\n end_gcode = M400\nM104 S0\nM140 S0\nM107\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG28 X R5\nG0 Y300 F2000\nM84\n color_change_gcode = M600 #limits From 755f2078fc59110168e8a152bccf3d74fcc4fc09 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Thu, 8 Apr 2021 14:13:40 +0200 Subject: [PATCH 043/154] updated config_update_url --- resources/profiles/INAT.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/INAT.ini b/resources/profiles/INAT.ini index 2d180d95476..81a4f90ef42 100644 --- a/resources/profiles/INAT.ini +++ b/resources/profiles/INAT.ini @@ -4,7 +4,7 @@ # Vendor name will be shown by the Config Wizard. name = INAT config_version = 0.0.2 -config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/INAT/ +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/INAT/ ### ### PRINTER LIST From 9e770e052cab9f6e11b8ef2515c77d2a37574ea4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 8 Apr 2021 14:35:58 +0200 Subject: [PATCH 044/154] Fix slow cancellation of rasterization step fixes #6253 --- src/libslic3r/SLAPrint.hpp | 25 +++++++++++++++++-------- src/libslic3r/SLAPrintSteps.cpp | 3 ++- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 74c71dc1e81..adb80c29ac7 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -390,16 +390,25 @@ public: virtual void apply(const SLAPrinterConfig &cfg) = 0; // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); - template void draw_layers(size_t layer_num, Fn &&drawfn) + template + void draw_layers( + size_t layer_num, + Fn && drawfn, + CancelFn cancelfn = []() { return false; }, + const EP & ep = {}) { m_layers.resize(layer_num); - sla::ccr::for_each(size_t(0), m_layers.size(), - [this, &drawfn] (size_t idx) { - sla::EncodedRaster& enc = m_layers[idx]; - auto rst = create_raster(); - drawfn(*rst, idx); - enc = rst->encode(get_encoder()); - }); + execution::for_each( + ep, size_t(0), m_layers.size(), + [this, &drawfn, &cancelfn](size_t idx) { + if (cancelfn()) return; + + sla::EncodedRaster &enc = m_layers[idx]; + auto rst = create_raster(); + drawfn(*rst, idx); + enc = rst->encode(get_encoder()); + }, + execution::max_concurrency(ep)); } }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index fc668620119..c393eb295d5 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1093,7 +1093,8 @@ void SLAPrint::Steps::rasterize() if(canceled()) return; // Print all the layers in parallel - m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); + m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn, + [this]() { return canceled(); }, ex_tbb); } std::string SLAPrint::Steps::label(SLAPrintObjectStep step) From c0434121f526fc094f1c361a05ecb306a3d8f8c5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 8 Apr 2021 15:29:40 +0200 Subject: [PATCH 045/154] New FDM support sparse infill zig-zag algorithm. Fixed some old support and infill issues. Fixes support problem #4295 Fixes Parts of interface layer extends beyond supports and cannot be printed Fixes support missing under horizontal overhang #6058 Fixes Slicer double-traces small sections of Rectilinear Supports, causes Fixes plastic buildup and nozzle crashes #4951 Fixes Add "Angle Interface layers" #2969 --- src/PrusaSlicer_app_msvc.cpp | 5 + src/libslic3r/BoundingBox.cpp | 17 +- src/libslic3r/ExtrusionEntity.hpp | 2 + src/libslic3r/Fill/Fill3DHoneycomb.cpp | 2 +- src/libslic3r/Fill/FillBase.cpp | 1306 +++++++++++++++++++++--- src/libslic3r/Fill/FillBase.hpp | 22 +- src/libslic3r/Fill/FillGyroid.cpp | 2 +- src/libslic3r/Fill/FillHoneycomb.cpp | 2 +- src/libslic3r/Fill/FillLine.cpp | 2 +- src/libslic3r/Fill/FillRectilinear.cpp | 280 +++-- src/libslic3r/Fill/FillRectilinear.hpp | 11 + src/libslic3r/Geometry.hpp | 23 +- src/libslic3r/MultiPoint.hpp | 1 + src/libslic3r/Point.hpp | 19 + src/libslic3r/Print.cpp | 7 +- src/libslic3r/PrintConfig.cpp | 4 +- src/libslic3r/PrintConfig.hpp | 2 +- src/libslic3r/SupportMaterial.cpp | 51 +- 18 files changed, 1473 insertions(+), 285 deletions(-) diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 7710d9eb7e9..2ccf2f1ffd2 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -217,6 +217,11 @@ int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, int wmain(int argc, wchar_t **argv) { #endif + // Allow the asserts to open message box, such message box allows to ignore the assert and continue with the application. + // Without this call, the seemingly same message box is being opened by the abort() function, but that is too late and + // the application will be killed even if "Ignore" button is pressed. + _set_error_mode(_OUT_TO_MSGBOX); + std::vector argv_extended; argv_extended.emplace_back(argv[0]); diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index eb4e042a08c..4f52c5108d5 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -225,24 +225,11 @@ BoundingBox3Base::max_size() const template coordf_t BoundingBox3Base::max_size() const; template coordf_t BoundingBox3Base::max_size() const; -// Align a coordinate to a grid. The coordinate may be negative, -// the aligned value will never be bigger than the original one. -static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; -} - void BoundingBox::align_to_grid(const coord_t cell_size) { if (this->defined) { - min(0) = _align_to_grid(min(0), cell_size); - min(1) = _align_to_grid(min(1), cell_size); + min(0) = Slic3r::align_to_grid(min(0), cell_size); + min(1) = Slic3r::align_to_grid(min(1), cell_size); } } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 2c43cd4af88..2f35083169e 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -203,6 +203,8 @@ public: void reverse() override; const Point& first_point() const override { return this->paths.front().polyline.points.front(); } const Point& last_point() const override { return this->paths.back().polyline.points.back(); } + size_t size() const { return this->paths.size(); } + bool empty() const { return this->paths.empty(); } double length() const override; ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 2ddca7fe4ad..95c26fbad42 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -147,7 +147,7 @@ void Fill3DHoneycomb::_fill_surface_single( // align bounding box to a multiple of our honeycomb grid module // (a module is 2*$distance since one $distance half-module is // growing while the other $distance half-module is shrinking) - bb.merge(_align_to_grid(bb.min, Point(2*distance, 2*distance))); + bb.merge(align_to_grid(bb.min, Point(2*distance, 2*distance))); // generate pattern Polylines polylines = makeGrid( diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index b4afd259177..6d1d94ff8cd 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -19,6 +19,8 @@ #include "FillRectilinear.hpp" #include "FillAdaptive.hpp" +// #define INFILL_DEBUG_OUTPUT + namespace Slic3r { Fill* Fill::new_from_type(const InfillPattern type) @@ -41,6 +43,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipOctagramSpiral: return new FillOctagramSpiral(); case ipAdaptiveCubic: return new FillAdaptive::Filler(); case ipSupportCubic: return new FillAdaptive::Filler(); + case ipSupportBase: return new FillSupportBase(); default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -253,15 +256,15 @@ std::pair path_lengths_along_contour(const ContourIntersectionPo } // Add contour points from interval (idx_start, idx_end> to polyline. -static inline void take_cw_full(Polyline &pl, const Points& contour, size_t idx_start, size_t idx_end) +static inline void take_cw_full(Polyline &pl, const Points &contour, size_t idx_start, size_t idx_end) { assert(! pl.empty() && pl.points.back() == contour[idx_start]); - size_t i = (idx_end == 0) ? contour.size() - 1 : idx_start - 1; + size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1; while (i != idx_end) { pl.points.emplace_back(contour[i]); if (i == 0) i = contour.size(); - --i; + -- i; } pl.points.emplace_back(contour[i]); } @@ -612,13 +615,13 @@ static inline bool line_rounded_thick_segment_collision( }; // Intersections with the inflated segment end points. - auto ray_circle_intersection_interval_extend = [&extend_interval, &line_v0](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) { + auto ray_circle_intersection_interval_extend = [&extend_interval](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) { std::pair pts; Vec2d p0 = line_pt - segment_pt; - double c = - line_pt.dot(p0); - if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.x(), line_vec.y(), line_vec.squaredNorm(), c, pts)) { - double tmin = (pts.first - p0).dot(line_v0); - double tmax = (pts.second - p0).dot(line_v0); + double lv2 = line_vec.squaredNorm(); + if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.y(), - line_vec.x(), lv2, - line_vec.y() * p0.x() + line_vec.x() * p0.y(), pts)) { + double tmin = (pts.first - p0).dot(line_vec) / lv2; + double tmax = (pts.second - p0).dot(line_vec) / lv2; if (tmin > tmax) std::swap(tmin, tmax); tmin = std::max(tmin, 0.); @@ -705,8 +708,6 @@ static inline bool cyclic_interval_inside_interval(double outer_low, double oute } #endif // NDEBUG -// #define INFILL_DEBUG_OUTPUT - #ifdef INFILL_DEBUG_OUTPUT static void export_infill_to_svg( // Boundary contour, along which the perimeter extrusions will be drawn. @@ -1099,61 +1100,216 @@ void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_s connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); } -void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); + +struct BoundaryInfillGraph { - assert(! infill_ordered.empty()); - assert(params.anchor_length >= 0.); - assert(params.anchor_length_max >= 0.01f); - assert(params.anchor_length_max >= params.anchor_length); - const double anchor_length = scale_(params.anchor_length); - const double anchor_length_max = scale_(params.anchor_length_max); + std::vector boundary; + std::vector> boundary_params; + std::vector map_infill_end_point_to_boundary; -#if 0 - append(polylines_out, infill_ordered); - return; -#endif + const Point& point(const ContourIntersectionPoint &cp) const { + assert(cp.contour_idx != size_t(-1)); + assert(cp.point_idx != size_t(-1)); + return this->boundary[cp.contour_idx][cp.point_idx]; + } - // 1) Add the end points of infill_ordered to boundary_src. - std::vector boundary; - std::vector> boundary_params; - boundary.assign(boundary_src.size(), Points()); - boundary_params.assign(boundary_src.size(), std::vector()); - // Mapping the infill_ordered end point to a (contour, point) of boundary. - static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); - std::vector map_infill_end_point_to_boundary(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected }); - { - // Project the infill_ordered end points onto boundary_src. - std::vector> intersection_points; - { - EdgeGrid::Grid grid; - grid.set_bbox(bbox.inflated(SCALED_EPSILON)); - grid.create(boundary_src, coord_t(scale_(10.))); - intersection_points.reserve(infill_ordered.size() * 2); - for (const Polyline &pl : infill_ordered) - for (const Point *pt : { &pl.points.front(), &pl.points.back() }) { - EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(*pt, coord_t(SCALED_EPSILON)); - if (cp.valid()) { - // The infill end point shall lie on the contour. - assert(cp.distance <= 3.); - intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1)); - } - } - std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair &cp1, const std::pair &cp2) { - return cp1.first.contour_idx < cp2.first.contour_idx || - (cp1.first.contour_idx == cp2.first.contour_idx && - (cp1.first.start_point_idx < cp2.first.start_point_idx || - (cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t))); - }); - } - auto it = intersection_points.begin(); - auto it_end = intersection_points.end(); - std::vector> boundary_intersection_points(boundary.size(), std::vector()); - for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) { + const Point& infill_end_point(size_t infill_end_point_idx) const { + return this->point(this->map_infill_end_point_to_boundary[infill_end_point_idx]); + } + + const Point interpolate_contour_point(const ContourIntersectionPoint &cp, double param) { + const Points &contour = this->boundary[cp.contour_idx]; + const std::vector &contour_params = this->boundary_params[cp.contour_idx]; + // Find the start of a contour segment with param. + auto it = std::lower_bound(contour_params.begin(), contour_params.end(), param); + if (*it != param) { + assert(it != contour_params.begin()); + -- it; + } + size_t i = it - contour_params.begin(); + if (i == contour.size()) + i = 0; + double t1 = contour_params[i]; + double t2 = next_value_modulo(i, contour_params); + return lerp(contour[i], next_value_modulo(i, contour), (param - t1) / (t2 - t1)); + } + + enum Direction { + Left, + Right, + Up, + Down, + Taken, + }; + + static Direction dir(const Point &p1, const Point &p2) { + return p1.x() == p2.x() ? + (p1.y() < p2.y() ? Up : Down) : + (p1.x() < p2.x() ? Right : Left); + } + + const Direction dir_prev(const ContourIntersectionPoint &cp) const { + assert(cp.prev_on_contour); + return cp.could_take_prev() ? + dir(this->point(cp), this->point(*cp.prev_on_contour)) : + Taken; + } + + const Direction dir_next(const ContourIntersectionPoint &cp) const { + assert(cp.next_on_contour); + return cp.could_take_next() ? + dir(this->point(cp), this->point(*cp.next_on_contour)) : + Taken; + } + + bool first(const ContourIntersectionPoint &cp) const { + return ((&cp - this->map_infill_end_point_to_boundary.data()) & 1) == 0; + } + + const ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) const { + return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)]; + } + + ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) { + return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)]; + } + + bool prev_vertical(const ContourIntersectionPoint &cp) const { + return this->point(cp).x() == this->point(*cp.prev_on_contour).x(); + } + + bool next_vertical(const ContourIntersectionPoint &cp) const { + return this->point(cp).x() == this->point(*cp.next_on_contour).x(); + } + +}; + + +// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines, +// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully +// Mark those short boundary segments. +static inline void mark_boundary_segments_overlapping_infill( + BoundaryInfillGraph &graph, + // Infill lines, either completely inside the boundary, or touching the boundary. + const Polylines &infill, + // Spacing (width) of the infill lines. + const double spacing) +{ + struct Linef { Vec2d a; Vec2d b; }; + + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const Points &contour = graph.boundary[cp.contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp.contour_idx]; + const Polyline &infill_polyline = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2]; + const double radius = 0.5 * (spacing + SCALED_EPSILON); + assert(infill_polyline.size() == 2); + const Linef infill_line { infill_polyline.points.front().cast(), infill_polyline.points.back().cast() }; + if (cp.could_take_next()) { + bool inside = true; + for (size_t i = cp.point_idx; i != cp.next_on_contour->point_idx; ) { + size_t j = next_idx_modulo(i, contour); + const Vec2d seg_pt2 = contour[j].cast(); + if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) { + // The segment is completely inside. + } else { + std::pair interval; + line_rounded_thick_segment_collision(contour[i].cast(), seg_pt2, infill_line.a, infill_line.b, radius, interval); + assert(interval.first == 0.); + double len_out = closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; + if (len_out < cp.contour_not_taken_length_next) { + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // thus at least some of the contour is outside and we will extrude this segment. + inside = false; + break; + } + } + if (closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_next) + break; + i = j; + } + if (inside) { + if (! cp.next_trimmed) + // The arc from cp to cp.next_on_contour was not trimmed yet, however it is completely overlapping the infill line. + cp.next_on_contour->trim_prev(0); + cp.trim_next(0); + } + } else + cp.trim_next(0); + if (cp.could_take_prev()) { + bool inside = true; + for (size_t i = cp.point_idx; i != cp.prev_on_contour->point_idx; ) { + size_t j = prev_idx_modulo(i, contour); + const Vec2d seg_pt2 = contour[j].cast(); + // Distance of the second segment line from the infill line. + if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) { + // The segment is completely inside. + } else { + std::pair interval; + line_rounded_thick_segment_collision(contour[i].cast(), seg_pt2, infill_line.a, infill_line.b, radius, interval); + assert(interval.first == 0.); + double len_out = closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; + if (len_out < cp.contour_not_taken_length_prev) { + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // thus at least some of the contour is outside and we will extrude this segment. + inside = false; + break; + } + } + if (closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_prev) + break; + i = j; + } + if (inside) { + if (! cp.prev_trimmed) + // The arc from cp to cp.prev_on_contour was not trimmed yet, however it is completely overlapping the infill line. + cp.prev_on_contour->trim_next(0); + cp.trim_prev(0); + } + } else + cp.trim_prev(0); + } +} + +BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, const double spacing) +{ + BoundaryInfillGraph out; + out.boundary.assign(boundary_src.size(), Points()); + out.boundary_params.assign(boundary_src.size(), std::vector()); + out.map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected }); + { + // Project the infill_ordered end points onto boundary_src. + std::vector> intersection_points; + { + EdgeGrid::Grid grid; + grid.set_bbox(bbox.inflated(SCALED_EPSILON)); + grid.create(boundary_src, coord_t(scale_(10.))); + intersection_points.reserve(infill_ordered.size() * 2); + for (const Polyline &pl : infill_ordered) + for (const Point *pt : { &pl.points.front(), &pl.points.back() }) { + EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(*pt, coord_t(SCALED_EPSILON)); + if (cp.valid()) { + // The infill end point shall lie on the contour. + assert(cp.distance <= 3.); + intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1)); + } + } + std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair &cp1, const std::pair &cp2) { + return cp1.first.contour_idx < cp2.first.contour_idx || + (cp1.first.contour_idx == cp2.first.contour_idx && + (cp1.first.start_point_idx < cp2.first.start_point_idx || + (cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t))); + }); + } + auto it = intersection_points.begin(); + auto it_end = intersection_points.end(); + std::vector> boundary_intersection_points(out.boundary.size(), std::vector()); + for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) { // Copy contour_src to contour_dst while adding intersection points. // Map infill end points map_infill_end_point_to_boundary to the newly inserted boundary points of contour_dst. // chain the points of map_infill_end_point_to_boundary along their respective contours. - const Polygon &contour_src = *boundary_src[idx_contour]; - Points &contour_dst = boundary[idx_contour]; + const Polygon &contour_src = *boundary_src[idx_contour]; + Points &contour_dst = out.boundary[idx_contour]; std::vector &contour_intersection_points = boundary_intersection_points[idx_contour]; ContourIntersectionPoint *pfirst = nullptr; ContourIntersectionPoint *pprev = nullptr; @@ -1164,18 +1320,18 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorfirst.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { - // Add these points to the destination contour. + contour_dst.emplace_back(ipt); + for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { + // Add these points to the destination contour. const Polyline &infill_line = infill_ordered[it->second / 2]; const Point &pt = (it->second & 1) ? infill_line.points.back() : infill_line.points.front(); //#ifndef NDEBUG // { -// const Vec2d pt1 = ipt.cast(); -// const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); +// const Vec2d pt1 = ipt.cast(); +// const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); // const Vec2d ptx = lerp(pt1, pt2, it->first.t); // assert(std::abs(ptx.x() - pt.x()) < SCALED_EPSILON); // assert(std::abs(ptx.y() - pt.y()) < SCALED_EPSILON); @@ -1187,8 +1343,8 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorsecond] = ContourIntersectionPoint{ idx_contour, idx_tjoint_pt }; - ContourIntersectionPoint *pthis = &map_infill_end_point_to_boundary[it->second]; + out.map_infill_end_point_to_boundary[it->second] = ContourIntersectionPoint{ /* it->second, */ idx_contour, idx_tjoint_pt }; + ContourIntersectionPoint *pthis = &out.map_infill_end_point_to_boundary[it->second]; if (pprev) { pprev->next_on_contour = pthis; pthis->prev_on_contour = pprev; @@ -1196,15 +1352,15 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectornext_on_contour = pfirst; pfirst->prev_on_contour = pprev; } - } - // Parametrize the new boundary with the intersection points inserted. - std::vector &contour_params = boundary_params[idx_contour]; - contour_params.assign(contour_dst.size() + 1, 0.); + } + // Parametrize the new boundary with the intersection points inserted. + std::vector &contour_params = out.boundary_params[idx_contour]; + contour_params.assign(contour_dst.size() + 1, 0.); for (size_t i = 1; i < contour_dst.size(); ++i) { contour_params[i] = contour_params[i - 1] + (contour_dst[i].cast() - contour_dst[i - 1].cast()).norm(); assert(contour_params[i] > contour_params[i - 1]); @@ -1225,18 +1381,18 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_not_taken_length_prev = closed_contour_distance_ccw(ip->prev_on_contour->param, ip->param, contour_length); ip->contour_not_taken_length_next = closed_contour_distance_ccw(ip->param, ip->next_on_contour->param, contour_length); } - } + } - assert(boundary.size() == boundary_src.size()); + assert(out.boundary.size() == boundary_src.size()); #if 0 // Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary. - assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), - [&boundary](const ContourIntersectionPoint &contour_point) { - return contour_point.contour_idx < boundary.size() && contour_point.point_idx < boundary[contour_point.contour_idx].size(); - })); + assert(std::all_of(out.map_infill_end_point_to_boundary.begin(), out.map_infill_end_point_to_boundary.end(), + [&out.boundary](const ContourIntersectionPoint &contour_point) { + return contour_point.contour_idx < out.boundary.size() && contour_point.point_idx < out.boundary[contour_point.contour_idx].size(); + })); #endif - // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. + // Mark the points and segments of split out.boundary as consumed if they are very close to some of the infill line. { // @supermerill used 2. * scale_(spacing) const double clip_distance = 1.7 * scale_(spacing); @@ -1244,37 +1400,31 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector merged_with(infill_ordered.size()); + return out; +} + +void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ + assert(! infill_ordered.empty()); + assert(params.anchor_length >= 0.); + assert(params.anchor_length_max >= 0.01f); + assert(params.anchor_length_max >= params.anchor_length); + const double anchor_length = scale_(params.anchor_length); + const double anchor_length_max = scale_(params.anchor_length_max); + +#if 0 + append(polylines_out, infill_ordered); + return; +#endif + + BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing); + + std::vector merged_with(infill_ordered.size()); std::iota(merged_with.begin(), merged_with.end(), 0); - struct ConnectionCost { - ConnectionCost(size_t idx_first, double cost, bool reversed) : idx_first(idx_first), cost(cost), reversed(reversed) {} - size_t idx_first; - double cost; - bool reversed; - }; - std::vector connections_sorted; - connections_sorted.reserve(infill_ordered.size() * 2 - 2); - for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { - const ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; - const ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; - if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) { - // End points on the same contour. Try to connect them. - std::pair len = path_lengths_along_contour(cp1, cp2, boundary_params[cp1->contour_idx].back()); - if (len.first < length_max) - connections_sorted.emplace_back(idx_chain - 1, len.first, false); - if (len.second < length_max) - connections_sorted.emplace_back(idx_chain - 1, len.second, true); - } - } - std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; }); auto get_and_update_merged_with = [&merged_with](size_t polyline_idx) -> size_t { for (size_t last = polyline_idx;;) { @@ -1293,9 +1443,35 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector connections_sorted; + connections_sorted.reserve(infill_ordered.size() * 2 - 2); + for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { + const ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; + const ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[idx_chain * 2]; + if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) { + // End points on the same contour. Try to connect them. + std::pair len = path_lengths_along_contour(cp1, cp2, graph.boundary_params[cp1->contour_idx].back()); + if (len.first < length_max) + connections_sorted.emplace_back(idx_chain - 1, len.first, false); + if (len.second < length_max) + connections_sorted.emplace_back(idx_chain - 1, len.second, true); + } + } + std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; }); + for (ConnectionCost &connection_cost : connections_sorted) { - ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; - ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; + ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; + ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; assert(cp1 != cp2); assert(cp1->contour_idx == cp2->contour_idx && cp1->contour_idx != boundary_idx_unconnected); if (cp1->consumed || cp2->consumed) @@ -1306,7 +1482,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorparam, cp_high->param, boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON); + assert(std::abs(length - closed_contour_distance_ccw(cp_low->param, cp_high->param, graph.boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON); could_connect = ! cp_low->next_trimmed && ! cp_high->prev_trimmed; if (could_connect && cp_low->next_on_contour != cp_high) { // Other end of cp1, may or may not be on the same contour as cp1. @@ -1329,14 +1505,14 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_idx], cp1, cp2, connection_cost.reversed); + take(infill_ordered[idx_first], infill_ordered[idx_second], graph.boundary[cp1->contour_idx], cp1, cp2, connection_cost.reversed); // Mark the second polygon as merged with the first one. merged_with[idx_second] = merged_with[idx_first]; infill_ordered[idx_second].points.clear(); } else { // Try to connect cp1 resp. cp2 with a piece of perimeter line. - take_limited(infill_ordered[idx_first], boundary[cp1->contour_idx], boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, anchor_length, line_half_width); - take_limited(infill_ordered[idx_second], boundary[cp1->contour_idx], boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, anchor_length, line_half_width); + take_limited(infill_ordered[idx_first], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, anchor_length, line_half_width); + take_limited(infill_ordered[idx_second], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, anchor_length, line_half_width); } } #endif @@ -1346,10 +1522,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector arches; - arches.reserve(map_infill_end_point_to_boundary.size()); - for (ContourIntersectionPoint &cp : map_infill_end_point_to_boundary) + arches.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) if (cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next()) - arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, boundary_params[cp.contour_idx].back()) }); + arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, graph.boundary_params[cp.contour_idx].back()) }); std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length < r.arc_length; }); //FIXME improve the Traveling Salesman problem with 2-opt and 3-opt local optimization. @@ -1358,10 +1534,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectornext_on_contour; - size_t polyline_idx1 = get_and_update_merged_with(((cp1 - map_infill_end_point_to_boundary.data()) / 2)); - size_t polyline_idx2 = get_and_update_merged_with(((cp2 - map_infill_end_point_to_boundary.data()) / 2)); - const Points &contour = boundary[cp1->contour_idx]; - const std::vector &contour_params = boundary_params[cp1->contour_idx]; + size_t polyline_idx1 = get_and_update_merged_with(((cp1 - graph.map_infill_end_point_to_boundary.data()) / 2)); + size_t polyline_idx2 = get_and_update_merged_with(((cp2 - graph.map_infill_end_point_to_boundary.data()) / 2)); + const Points &contour = graph.boundary[cp1->contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp1->contour_idx]; if (polyline_idx1 != polyline_idx2) { Polyline &polyline1 = infill_ordered[polyline_idx1]; Polyline &polyline2 = infill_ordered[polyline_idx2]; @@ -1392,10 +1568,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &contour_params = boundary_params[contour_point.contour_idx]; + const Points &contour = graph.boundary[contour_point.contour_idx]; + const std::vector &contour_params = graph.boundary_params[contour_point.contour_idx]; double lprev = contour_point.could_connect_prev() ? path_length_along_contour_ccw(contour_point.prev_on_contour, &contour_point, contour_params.back()) : @@ -1403,7 +1579,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector::max(); - size_t polyline_idx = get_and_update_merged_with(((&contour_point - map_infill_end_point_to_boundary.data()) / 2)); + size_t polyline_idx = get_and_update_merged_with(((&contour_point - graph.map_infill_end_point_to_boundary.data()) / 2)); Polyline &polyline = infill_ordered[polyline_idx]; assert(! polyline.empty()); assert(contour[contour_point.point_idx] == polyline.points.front() || contour[contour_point.point_idx] == polyline.points.back()); @@ -1415,7 +1591,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &contour_param = graph.boundary_params[cp.contour_idx]; + const Point &pt = contour[cp.point_idx]; + const bool first = graph.first(cp); + int extend_next_idx = -1; + int extend_prev_idx = -1; + coord_t dist_y_prev; + coord_t dist_y_next; + double arc_len_prev; + double arc_len_next; + + if (! graph.next_vertical(cp)){ + size_t i = cp.point_idx; + size_t j = next_idx_modulo(i, contour); + while (j != cp.next_on_contour->point_idx) { + //const Point &p1 = contour[i]; + const Point &p2 = contour[j]; + if (std::abs(p2.x() - pt.x()) > dist_max_x) + break; + i = j; + j = next_idx_modulo(j, contour); + } + if (i != cp.point_idx) { + const Point &p2 = contour[i]; + coord_t dist_y = p2.y() - pt.y(); + if (first) + dist_y = - dist_y; + if (dist_y > dist_min_y) { + arc_len_next = closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[i], contour_param.back()); + if (arc_len_next < cp.contour_not_taken_length_next) { + extend_next_idx = i; + dist_y_next = dist_y; + } + } + } + } + + if (! graph.prev_vertical(cp)) { + size_t i = cp.point_idx; + size_t j = prev_idx_modulo(i, contour); + while (j != cp.prev_on_contour->point_idx) { + //const Point &p1 = contour[i]; + const Point &p2 = contour[j]; + if (std::abs(p2.x() - pt.x()) > dist_max_x) + break; + i = j; + j = prev_idx_modulo(j, contour); + } + if (i != cp.point_idx) { + const Point &p2 = contour[i]; + coord_t dist_y = p2.y() - pt.y(); + if (first) + dist_y = - dist_y; + if (dist_y > dist_min_y) { + arc_len_prev = closed_contour_distance_ccw(contour_param[i], contour_param[cp.point_idx], contour_param.back()); + if (arc_len_prev < cp.contour_not_taken_length_prev) { + extend_prev_idx = i; + dist_y_prev = dist_y; + } + } + } + } + + if (extend_prev_idx >= 0 && extend_next_idx >= 0) + // Which side to move the point? + dist_y_prev < dist_y_next ? extend_prev_idx : extend_next_idx = -1; + + assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed); + assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed); + Polyline &infill_line = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2]; + if (extend_prev_idx >= 0) { + if (first) + infill_line.reverse(); + take_cw_full(infill_line, contour, cp.point_idx, extend_prev_idx); + if (first) + infill_line.reverse(); + cp.point_idx = extend_prev_idx; + if (cp.prev_trimmed) + cp.contour_not_taken_length_prev -= arc_len_prev; + else + cp.contour_not_taken_length_prev = cp.prev_on_contour->contour_not_taken_length_next = + closed_contour_distance_ccw(contour_param[cp.prev_on_contour->point_idx], contour_param[cp.point_idx], contour_param.back()); + cp.trim_next(0); + cp.next_on_contour->prev_trimmed = true; + } else if (extend_next_idx >= 0) { + if (first) + infill_line.reverse(); + take_ccw_full(infill_line, contour, cp.point_idx, extend_next_idx); + if (first) + infill_line.reverse(); + cp.point_idx = extend_next_idx; + cp.trim_prev(0); + cp.prev_on_contour->next_trimmed = true; + if (cp.next_trimmed) + cp.contour_not_taken_length_next -= arc_len_next; + else + cp.contour_not_taken_length_next = cp.next_on_contour->contour_not_taken_length_prev = + closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[cp.next_on_contour->point_idx], contour_param.back()); + } + } +} + +// Called by Fill::connect_base_support() as part of the sparse support infill generator. +// Emit contour loops tracing the contour from tbegin to tend inside a band of (left, right). +// The contour is supposed to enter the "forbidden" zone outside of the (left, right) band at tbegin and also at tend. +static inline void emit_loops_in_band( + // Vertical band, which will trim the contour between tbegin and tend. + coord_t left, + coord_t right, + // Contour and its parametrization. + const Points &contour, + const std::vector &contour_params, + // Span of the parameters of an arch to trim with the vertical band. + double tbegin, + double tend, + // Minimum arch length to put into polylines_out. Shorter arches are not necessary to support a dense support infill. + double min_length, + Polylines &polylines_out) +{ + assert(left < right); + assert(contour.size() + 1 == contour_params.size()); + assert(contour.size() >= 3); +#ifndef NDEBUG + double contour_length = contour_params.back(); + assert(tbegin >= 0 && tbegin < contour_length); + assert(tend >= 0 && tend < contour_length); + assert(min_length > 0); +#endif // NDEBUG + + // Find iterators of the range of segments, where the first and last segment contains tbegin and tend. + size_t ibegin, iend; + { + auto it_begin = std::lower_bound(contour_params.begin(), contour_params.end(), tbegin); + auto it_end = std::lower_bound(contour_params.begin(), contour_params.end(), tend); + assert(it_begin != contour_params.end()); + assert(it_end != contour_params.end()); + if (*it_begin != tbegin) { + assert(it_begin != contour_params.begin()); + -- it_begin; + } + ibegin = it_begin - contour_params.begin(); + iend = it_end - contour_params.begin(); + } + + if (ibegin == contour.size()) + ibegin = 0; + if (iend == contour.size()) + iend = 0; + assert(ibegin != iend); + + // Trim the start and end segment to calculate start and end points. + Point pbegin, pend; + { + double t1 = contour_params[ibegin]; + double t2 = next_value_modulo(ibegin, contour_params); + pbegin = lerp(contour[ibegin], next_value_modulo(ibegin, contour), (tbegin - t1) / (t2 - t1)); + t1 = contour_params[iend]; + t2 = prev_value_modulo(iend, contour_params); + pend = lerp(contour[iend], prev_value_modulo(iend, contour), (tend - t1) / (t2 - t1)); + } + + // Trace the contour from ibegin to iend. + enum Side { + Left, + Right, + Mid, + Unknown + }; + + enum InOutBand { + Entering, + Leaving, + }; + + class State { + public: + State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) : + m_left(left), m_right(right), m_min_length(min_length), m_polylines_out(polylines_out) {} + + void add_inner_point(const Point* p) + { + m_polyline.points.emplace_back(*p); + } + + void add_outer_point(const Point* p) + { + if (m_polyline_end > 0) + m_polyline.points.emplace_back(*p); + } + + void add_interpolated_point(const Point* p1, const Point* p2, Side side, InOutBand inout) + { + assert(side == Left || side == Right); + + coord_t x = side == Left ? m_left : m_right; + coord_t y = p1->y() + coord_t(double(x - p1->x()) * double(p2->y() - p1->y()) / double(p2->x() - p1->x())); + + if (inout == Leaving) { + assert(m_polyline_end == 0); + m_polyline_end = m_polyline.size(); + m_polyline.points.emplace_back(x, y); + } else { + assert(inout == Entering); + if (m_polyline_end > 0) { + if ((this->side1 == Left) == (y - m_polyline.points[m_polyline_end].y() < 0)) { + // Emit the vertical segment. Remove the point, where the source contour was split the last time at m_left / m_right. + m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end); + } else { + // Don't emit the vertical segment, split the contour. + this->finalize(); + m_polyline.points.emplace_back(x, y); + } + m_polyline_end = 0; + } else + m_polyline.points.emplace_back(x, y); + } + }; + + void finalize() + { + m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end, m_polyline.points.end()); + if (! m_polyline.empty()) { + if (! m_polylines_out.empty() && (m_polylines_out.back().points.back() - m_polyline.points.front()).cast().squaredNorm() < SCALED_EPSILON) + m_polylines_out.back().points.insert(m_polylines_out.back().points.end(), m_polyline.points.begin() + 1, m_polyline.points.end()); + else if (m_polyline.length() > m_min_length) + m_polylines_out.emplace_back(std::move(m_polyline)); + m_polyline.clear(); + } + }; + + private: + coord_t m_left; + coord_t m_right; + double m_min_length; + Polylines &m_polylines_out; + + Polyline m_polyline; + size_t m_polyline_end { 0 }; + Polyline m_overlapping; + + public: + Side side1 { Unknown }; + Side side2 { Unknown }; + }; + + State state { left, right, min_length, polylines_out }; + + const Point *p1 = &pbegin; + auto side = [left, right](const Point* p) { + coord_t x = p->x(); + return x < left ? Left : x > right ? Right : Mid; + }; + state.side1 = side(p1); + if (state.side1 == Mid) + state.add_inner_point(p1); + + for (size_t i = ibegin; i != iend; ) { + size_t inext = i + 1; + if (inext == contour.size()) + inext = 0; + const Point *p2 = inext == iend ? &pend : &contour[inext]; + state.side2 = side(p2); + if (state.side1 == Mid) { + if (state.side2 == Mid) { + // Inside the band. + state.add_inner_point(p2); + } else { + // From intisde the band to the outside of the band. + state.add_interpolated_point(p1, p2, state.side2, Leaving); + state.add_outer_point(p2); + } + } else if (state.side2 == Mid) { + // From outside the band into the band. + state.add_interpolated_point(p1, p2, state.side1, Entering); + state.add_inner_point(p2); + } else if (state.side1 != state.side2) { + // Both points outside the band. + state.add_interpolated_point(p1, p2, state.side1, Entering); + state.add_interpolated_point(p1, p2, state.side2, Leaving); + } else { + // Complete segment is outside. + assert((state.side1 == Left && state.side2 == Left) || (state.side1 == Right && state.side2 == Right)); + state.add_outer_point(p2); + } + state.side1 = state.side2; + p1 = p2; + i = inext; + } + state.finalize(); +} + +#ifdef INFILL_DEBUG_OUTPUT +static void export_partial_infill_to_svg(const std::string &path, const BoundaryInfillGraph &graph, const Polylines &infill, const Polylines &emitted) +{ + Polygons polygons; + for (const Points &pts : graph.boundary) + polygons.emplace_back(pts); + BoundingBox bbox = get_extents(polygons); + bbox.merge(get_extents(infill)); + ::Slic3r::SVG svg(path, bbox); + svg.draw(union_ex(polygons)); + svg.draw(infill, "blue"); + svg.draw(emitted, "darkblue"); + for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) + svg.draw(graph.point(cp), cp.consumed ? "red" : "green", scaled(0.2)); + for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed); + assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed); + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + Polyline pl { graph.point(cp) }; + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); + svg.draw(pl, cp.could_take_next() ? "lime" : "magenta", scaled(0.1)); + } + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + Polyline pl { graph.point(cp) }; + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + svg.draw(pl, cp.could_take_prev() ? "lime" : "magenta", scaled(0.1)); + } + } +} +#endif // INFILL_DEBUG_OUTPUT + +// To classify perimeter segments connecting infill lines, whether they are required for structural stability of the supports. +struct SupportArcCost +{ + // Connecting one end of an infill line to the other end of the same infill line. + bool self_loop { false }; + // Some of the arc touches some infill line. + bool open { false }; + // How needed is this arch for support structural stability. + // Zero means don't take. The higher number, the more likely it is to take the arc. + double cost { 0 }; +}; + +static double evaluate_support_arch_cost(const Polyline &pl) +{ + Point front = pl.points.front(); + Point back = pl.points.back(); + + coord_t ymin = front.y(); + coord_t ymax = back.y(); + if (ymin > ymax) + std::swap(ymin, ymax); + + double dmax = 0; + // Maximum distance in Y axis out of the (ymin, ymax) band and from the (front, back) line. + struct Linef { Vec2d a, b; }; + Linef line { front.cast(), back.cast() }; + for (const Point pt : pl.points) + dmax = std::max(std::max(dmax, line_alg::distance_to(line, Vec2d(pt.cast()))), std::max(pt.y() - ymax, ymin - pt.y())); + return dmax; +} + +// Costs for prev / next arch of each infill line end point. +static inline std::vector evaluate_support_arches(Polylines &infill, BoundaryInfillGraph &graph, const double spacing, const FillParams ¶ms) +{ + std::vector arches(graph.map_infill_end_point_to_boundary.size() * 2); + + Polyline pl; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + // Not a losed loop, such loops should already be consumed. + assert(cp.next_on_contour != &cp); + const size_t infill_line_idx = &cp - graph.map_infill_end_point_to_boundary.data(); + const bool first = (infill_line_idx & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + + SupportArcCost &out_prev = arches[infill_line_idx * 2]; + SupportArcCost &out_next = *(&out_prev + 1); + out_prev.self_loop = cp.prev_on_contour == other_end; + out_prev.open = cp.prev_trimmed; + out_next.self_loop = cp.next_on_contour == other_end; + out_next.open = cp.next_trimmed; + + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + pl.clear(); + pl.points.emplace_back(graph.point(cp)); + if (cp.next_trimmed) + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); + else + take_ccw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx); + out_next.cost = evaluate_support_arch_cost(pl); + } + + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + pl.clear(); + pl.points.emplace_back(graph.point(cp)); + if (cp.prev_trimmed) + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + else + take_cw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx); + out_prev.cost = evaluate_support_arch_cost(pl); + } + } + + return arches; +} + +// Both the poly_with_offset and polylines_out are rotated, so the infill lines are strictly vertical. +void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ +// assert(! infill_ordered.empty()); + assert(params.anchor_length >= 0.); + assert(params.anchor_length_max >= 0.01f); + assert(params.anchor_length_max >= params.anchor_length); + + BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing); + +#ifdef INFILL_DEBUG_OUTPUT + static int iRun = 0; + ++ iRun; + export_partial_infill_to_svg(debug_out_path("connect_base_support-initial-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + const double line_half_width = 0.5 * scale_(spacing); + const double line_spacing = scale_(spacing) / params.density; + const double min_arch_length = 1.3 * line_spacing; + const double trim_length = line_half_width * 0.3; + +// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines, +// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully. +// Mark those short boundary segments. + mark_boundary_segments_overlapping_infill(graph, infill_ordered, scale_(spacing)); + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-marked-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Detect loops with zero infill end points connected. + // Extrude these loops as perimeters. + { + std::vector num_boundary_contour_infill_points(graph.boundary.size(), 0); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) + ++ num_boundary_contour_infill_points[cp.contour_idx]; + for (size_t i = 0; i < num_boundary_contour_infill_points.size(); ++ i) + if (num_boundary_contour_infill_points[i] == 0 && graph.boundary_params[i].back() > trim_length + 0.5 * line_spacing) { + // Emit a perimeter. + Polyline pl(graph.boundary[i]); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(trim_length); + if (pl.size() > 1) + polylines_out.emplace_back(std::move(pl)); + } + } + + // Before processing the boundary arches, emit those arches, which were trimmed by the infill lines at both sides, but which + // depart from the infill line at least once after touching the infill line. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.next_on_contour && cp.next_trimmed && cp.next_on_contour->prev_trimmed) { + // The arch is leaving one infill line to end up at the same infill line or at the neighbouring one. + // The arch is touching one of those infill lines at least once. + // Trace those arches and emit their parts, which are not attached to the end points and they are not overlapping with the two infill lines mentioned. + bool first = graph.first(cp); + coord_t left = graph.point(cp).x(); + coord_t right = left; + if (first) { + left += line_half_width; + right += line_spacing - line_half_width; + } else { + left -= line_spacing - line_half_width; + right -= line_half_width; + } + double param_start = cp.param + cp.contour_not_taken_length_next; + double param_end = cp.next_on_contour->param - cp.next_on_contour->contour_not_taken_length_prev; + double contour_length = graph.boundary_params[cp.contour_idx].back(); + if (param_start >= contour_length) + param_start -= contour_length; + if (param_end < 0) + param_end += contour_length; + // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param). + assert(cyclic_interval_inside_interval(cp.param, cp.next_on_contour->param, param_start, param_end, contour_length)); + emit_loops_in_band(left, right, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], param_start, param_end, 0.5 * line_spacing, polylines_out); + } + } +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-excess-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + base_support_extend_infill_lines(infill_ordered, graph, spacing, params); + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-extended-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + std::vector merged_with(infill_ordered.size()); + std::iota(merged_with.begin(), merged_with.end(), 0); + auto get_and_update_merged_with = [&graph, &merged_with](const ContourIntersectionPoint *cp) -> size_t { + size_t polyline_idx = (cp - graph.map_infill_end_point_to_boundary.data()) / 2; + for (size_t last = polyline_idx;;) { + size_t lower = merged_with[last]; + assert(lower <= last); + if (lower == last) { + merged_with[polyline_idx] = last; + return last; + } + last = lower; + } + assert(false); + return std::numeric_limits::max(); + }; + + auto vertical = [](BoundaryInfillGraph::Direction dir) { + return dir == BoundaryInfillGraph::Up || dir == BoundaryInfillGraph::Down; + }; + // When both left / right arch connected to cp is vertical (ends up at the same vertical infill line), which one to take? + auto take_vertical_prev = [](const ContourIntersectionPoint &cp) { + return cp.prev_trimmed == cp.next_trimmed ? + // Both are either trimmed or not trimmed. Take the longer contour. + cp.contour_not_taken_length_prev > cp.contour_not_taken_length_next : + // One is trimmed, the other is not trimmed. Take the not trimmed. + ! cp.prev_trimmed && cp.next_trimmed; + }; + + // Connect infill lines at cp and cpo_next_on_contour. + // If the complete arch cannot be taken, then + // if (take_first) + // take the infill line at cp and an arc from cp towards cp.next_on_contour. + // else + // take the infill line at cp_next_on_contour and an arc from cp.next_on_contour towards cp. + // If cp1 == next_on_contour (a single infill line is connected to a contour, this is a valid case for contours with holes), + // then extrude the full circle. + // Nothing is done if the arch could no more be taken (one of it end points were consumed already). + auto take_next = [&graph, &infill_ordered, &merged_with, get_and_update_merged_with, line_half_width, trim_length](ContourIntersectionPoint &cp, bool take_first) { + // Indices of the polylines to be connected by a perimeter segment. + ContourIntersectionPoint *cp1 = &cp; + ContourIntersectionPoint *cp2 = cp.next_on_contour; + assert(cp1->next_trimmed == cp2->prev_trimmed); + //assert(cp1->next_trimmed || cp1->consumed == cp2->consumed); + if (take_first ? cp1->consumed : cp2->consumed) + return; + size_t polyline_idx1 = get_and_update_merged_with(cp1); + size_t polyline_idx2 = get_and_update_merged_with(cp2); + Polyline &polyline1 = infill_ordered[polyline_idx1]; + Polyline &polyline2 = infill_ordered[polyline_idx2]; + const Points &contour = graph.boundary[cp1->contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp1->contour_idx]; + assert(cp1->consumed || contour[cp1->point_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back()); + assert(cp2->consumed || contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back()); + bool trimmed = take_first ? cp1->next_trimmed : cp2->prev_trimmed; + if (! trimmed) { + // Trim the end if closing a loop or making a T-joint. + trimmed = cp1 == cp2 || polyline_idx1 == polyline_idx2 || (take_first ? cp2->consumed : cp1->consumed); + if (! trimmed) { + const bool cp1_first = ((cp1 - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint* cp1_other = cp1_first ? cp1 + 1 : cp1 - 1; + // Self loop, connecting the end points of the same infill line. + trimmed = cp2 == cp1_other; + } + if (trimmed) /* [[unlikely]] */ { + // Single end point on a contour. This may happen on contours with holes. Extrude a loop. + // Or a self loop, connecting the end points of the same infill line. + // Or closing a chain of infill lines. This may happen if infilling a contour with a hole. + double len = cp1 == cp2 ? contour_params.back() : path_length_along_contour_ccw(cp1, cp2, contour_params.back()); + if (take_first) { + cp1->trim_next(std::max(0., len - trim_length - SCALED_EPSILON)); + cp2->trim_prev(0); + } else { + cp1->trim_next(0); + cp2->trim_prev(std::max(0., len - trim_length - SCALED_EPSILON)); + } + } + } + if (trimmed) { + if (take_first) + take_limited(polyline1, contour, contour_params, cp1, cp2, false, 1e10, line_half_width); + else + take_limited(polyline2, contour, contour_params, cp2, cp1, true, 1e10, line_half_width); + } else if (! cp1->consumed && ! cp2->consumed) { + if (contour[cp1->point_idx] == polyline1.points.front()) + polyline1.reverse(); + if (contour[cp2->point_idx] == polyline2.points.back()) + polyline2.reverse(); + take(polyline1, polyline2, contour, cp1, cp2, false); + // Mark the second polygon as merged with the first one. + if (polyline_idx2 < polyline_idx1) { + polyline2 = std::move(polyline1); + polyline1.points.clear(); + merged_with[polyline_idx1] = merged_with[polyline_idx2]; + } else { + polyline2.points.clear(); + merged_with[polyline_idx2] = merged_with[polyline_idx1]; + } + } + }; + + // Consume all vertical arches. If a vertical arch is touching a neighboring vertical infill line, thus the vertical arch is trimmed, + // only consume the trimmed part if it is longer than min_arch_length. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.contour_idx != boundary_idx_unconnected); + if (cp.consumed) + continue; + const ContourIntersectionPoint &cp_other = graph.other(cp); + assert((cp.next_on_contour == &cp_other) == (cp_other.prev_on_contour == &cp)); + assert((cp.prev_on_contour == &cp_other) == (cp_other.next_on_contour == &cp)); + BoundaryInfillGraph::Direction dir_prev = graph.dir_prev(cp); + BoundaryInfillGraph::Direction dir_next = graph.dir_next(cp); + // Following code will also consume contours with just a single infill line attached. (cp1->next_on_contour == cp1). + assert((cp.next_on_contour == &cp) == (cp.prev_on_contour == &cp)); + bool can_take_prev = vertical(dir_prev) && ! cp.prev_on_contour->consumed && cp.prev_on_contour != &cp_other; + bool can_take_next = vertical(dir_next) && ! cp.next_on_contour->consumed && cp.next_on_contour != &cp_other; + if (can_take_prev && (! can_take_next || take_vertical_prev(cp))) { + if (! cp.prev_trimmed || cp.contour_not_taken_length_prev > min_arch_length) + // take previous + take_next(*cp.prev_on_contour, false); + } else if (can_take_next) { + if (! cp.next_trimmed || cp.contour_not_taken_length_next > min_arch_length) + // take next + take_next(cp, true); + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-vertical-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + const std::vector arches = evaluate_support_arches(infill_ordered, graph, spacing, params); + static const double cost_low = line_spacing * 1.3; + static const double cost_high = line_spacing * 2.; + static const double cost_veryhigh = line_spacing * 3.; + + { + std::vector selected; + selected.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.consumed) + continue; + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + double cost_min = cost_prev.cost; + double cost_max = cost_next.cost; + if (cost_min > cost_max) + std::swap(cost_min, cost_max); + if (cost_max < cost_low || cost_min > cost_high) + // Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken. + continue; + const double cost_diff_relative = (cost_max - cost_min) / cost_max; + if (cost_diff_relative < 0.25) + // Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken. + continue; + if (cost_prev.cost > cost_low) + selected.emplace_back(&cost_prev); + if (cost_next.cost > cost_low) + selected.emplace_back(&cost_next); + } + // Take the longest arch first. + std::sort(selected.begin(), selected.end(), [](const auto *l, const auto *r) { return l->cost > r->cost; }); + // And connect along the arches. + for (const SupportArcCost *arc : selected) { + ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(arc - arches.data()) / 2]; + if (! cp.consumed) { + bool prev = ((arc - arches.data()) & 1) == 0; + if (prev) + take_next(*cp.prev_on_contour, false); + else + take_next(cp, true); + } + } + } + +#if 0 + { + // Connect infill lines with long horizontal arches. Only take a horizontal arch, if it will not block + // the end caps (vertical arches) at the other side of the infill line. + struct Arc { + ContourIntersectionPoint *intersection; + double arc_length; + bool take_next; + }; + std::vector arches; + arches.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.consumed) + continue; + // Not a losed loop, such loops should already be consumed. + assert(cp.next_on_contour != &cp); + const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + const bool loop_next = cp.next_on_contour == other_end; + if (! loop_next && cp.could_connect_next()) { + if (cp.contour_not_taken_length_next > min_arch_length) { + // Try both directions. This is useful to be able to close a loop back to the same line to take a long arch. + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + arches.push_back({ cp.next_on_contour, cp.contour_not_taken_length_next, false }); + } + } else { + //bool first = ((&cp - graph.map_infill_end_point_to_boundary) & 1) == 0; + if (cp.prev_trimmed && cp.could_take_prev()) { + //FIXME trace the trimmed line to decide what priority to assign to it. + // Is the end point close to the current vertical line or to the other vertical line? + const Point &pt = graph.point(cp); + const Point &prev = graph.point(*cp.prev_on_contour); + if (std::abs(pt.x() - prev.x()) < coord_t(0.5 * line_spacing)) { + // End point on the same line. + // Measure maximum distance from the current vertical line. + if (cp.contour_not_taken_length_prev > 0.5 * line_spacing) + arches.push_back({ &cp, cp.contour_not_taken_length_prev, false }); + } else { + // End point on the other line. + if (cp.contour_not_taken_length_prev > min_arch_length) + arches.push_back({ &cp, cp.contour_not_taken_length_prev, false }); + } + } + if (cp.next_trimmed && cp.could_take_next()) { + //FIXME trace the trimmed line to decide what priority to assign to it. + const Point &pt = graph.point(cp); + const Point &next = graph.point(*cp.next_on_contour); + if (std::abs(pt.x() - next.x()) < coord_t(0.5 * line_spacing)) { + // End point on the same line. + // Measure maximum distance from the current vertical line. + if (cp.contour_not_taken_length_next > 0.5 * line_spacing) + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + } else { + // End point on the other line. + if (cp.contour_not_taken_length_next > min_arch_length) + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + } + } + } + } + // Take the longest arch first. + std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length > r.arc_length; }); + // And connect along the arches. + for (Arc &arc : arches) + if (arc.take_next) + take_next(*arc.intersection, true); + else + take_next(*arc.intersection->prev_on_contour, false); + } +#endif + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-arches-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Traverse the unconnected lines in a zig-zag fashion, left to right only. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.contour_idx != boundary_idx_unconnected); + if (cp.consumed) + continue; + bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + if (first) { + // Only connect if the two lines are not connected by the same line already. + if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour)) + take_next(cp, true); + } else { + if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour)) + take_next(*cp.prev_on_contour, false); + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-zigzag-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Add the left caps. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + const bool loop_next = cp.next_on_contour == other_end; + const bool loop_prev = other_end->next_on_contour == &cp; +#ifndef NDEBUG + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + assert(cost_prev.self_loop == loop_prev); + assert(cost_next.self_loop == loop_next); +#endif // NDEBUG + if (loop_prev && cp.could_take_prev()) + take_next(*cp.prev_on_contour, false); + if (loop_next && cp.could_take_next()) + take_next(cp, true); + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-caps-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Connect with T joints using long arches. Loops could be created only if a very long arc has to be added. + { + std::vector candidates; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.could_take_prev()) + candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]); + if (cp.could_take_next()) + candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2 + 1]); + } + std::sort(candidates.begin(), candidates.end(), [](auto *c1, auto *c2) { return c1->cost > c2->cost; }); + for (const SupportArcCost *candidate : candidates) { + ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(candidate - arches.data()) / 2]; + bool prev = ((candidate - arches.data()) & 1) == 0; + if (prev) { + if (cp.could_take_prev() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour) || candidate->cost > cost_high)) + take_next(*cp.prev_on_contour, false); + } else { + if (cp.could_take_next() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour) || candidate->cost > cost_high)) + take_next(cp, true); + } + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-Tjoints-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Add very long arches and reasonably long caps even if both of its end points were already consumed. + const double cap_cost = 0.5 * line_spacing; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + if (cp.contour_not_taken_length_prev > SCALED_EPSILON && + (cost_prev.self_loop ? + cost_prev.cost > cap_cost : + cost_prev.cost > cost_veryhigh)) { + assert(cp.consumed && (cp.prev_on_contour->consumed || cp.prev_trimmed)); + Polyline pl { graph.point(cp) }; + if (! cp.prev_trimmed) { + cp.trim_prev(cp.contour_not_taken_length_prev - line_half_width); + cp.prev_on_contour->trim_next(0); + } + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + cp.trim_prev(0); + pl.clip_start(line_half_width); + polylines_out.emplace_back(std::move(pl)); + } + } + if (cp.contour_not_taken_length_next > SCALED_EPSILON && + (cost_next.self_loop ? + cost_next.cost > cap_cost : + cost_next.cost > cost_veryhigh)) { + assert(cp.consumed && (cp.next_on_contour->consumed || cp.next_trimmed)); + Polyline pl { graph.point(cp) }; + if (! cp.next_trimmed) { + cp.trim_next(cp.contour_not_taken_length_next - line_half_width); + cp.next_on_contour->trim_prev(0); + } + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); // line_half_width); + cp.trim_next(0); + pl.clip_start(line_half_width); + polylines_out.emplace_back(std::move(pl)); + } + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-final-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); })); + for (Polyline &pl : infill_ordered) + if (! pl.empty()) + polylines_out.emplace_back(std::move(pl)); +} + +void Fill::connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ + auto polygons_src = reserve_vector(boundary_src.size()); + for (const Polygon &polygon : boundary_src) + polygons_src.emplace_back(&polygon); + + connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index c09b70bca0f..eb9c4f1cc30 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -133,26 +133,10 @@ public: static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); static void connect_infill(Polylines &&infill_ordered, const std::vector &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms); - static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); + static void connect_base_support(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); + static void connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); - // Align a coordinate to a grid. The coordinate may be negative, - // the aligned value will never be bigger than the original one. - static coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; - } - static Point _align_to_grid(Point coord, Point spacing) - { return Point(_align_to_grid(coord(0), spacing(0)), _align_to_grid(coord(1), spacing(1))); } - static coord_t _align_to_grid(coord_t coord, coord_t spacing, coord_t base) - { return base + _align_to_grid(coord - base, spacing); } - static Point _align_to_grid(Point coord, Point spacing, Point base) - { return Point(_align_to_grid(coord(0), spacing(0), base(0)), _align_to_grid(coord(1), spacing(1), base(1))); } + static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); }; } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 5797c47a5c6..ff2d049cfdd 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -166,7 +166,7 @@ void FillGyroid::_fill_surface_single( coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); // align bounding box to a multiple of our grid module - bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); + bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); // generate pattern Polylines polylines = make_gyroid_waves( diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index 5e700008886..f7f79ae833b 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -47,7 +47,7 @@ void FillHoneycomb::_fill_surface_single( // extend bounding box so that our pattern will be aligned with other layers // $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one // The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough. - bounding_box.merge(_align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height))); + bounding_box.merge(align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height))); } coord_t x = bounding_box.min(0); diff --git a/src/libslic3r/Fill/FillLine.cpp b/src/libslic3r/Fill/FillLine.cpp index 1cb9b2244d1..6a0a19efd20 100644 --- a/src/libslic3r/Fill/FillLine.cpp +++ b/src/libslic3r/Fill/FillLine.cpp @@ -31,7 +31,7 @@ void FillLine::_fill_surface_single( } else { // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. - bounding_box.merge(_align_to_grid( + bounding_box.merge(align_to_grid( bounding_box.min, Point(this->_line_spacing, this->_line_spacing), direction.second.rotated(- direction.first))); diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 07f41de3838..baf57f4269b 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -798,33 +798,44 @@ static std::vector slice_region_by_vertical_lines(con assert(l <= this_x); assert(r >= this_x); // Calculate the intersection position in y axis. x is known. - if (p1(0) == this_x) { - if (p2(0) == this_x) { + if (p1.x() == this_x) { + if (p2.x() == this_x) { // Ignore strictly vertical segments. continue; } - is.pos_p = p1(1); + const Point &p0 = prev_value_modulo(iPrev, contour); + if (int64_t(p0.x() - p1.x()) * int64_t(p2.x() - p1.x()) > 0) { + // Ignore points of a contour touching the infill line from one side. + continue; + } + is.pos_p = p1.y(); is.pos_q = 1; - } else if (p2(0) == this_x) { - is.pos_p = p2(1); + } else if (p2.x() == this_x) { + const Point &p3 = next_value_modulo(iSegment, contour); + if (int64_t(p3.x() - p2.x()) * int64_t(p1.x() - p2.x()) > 0) { + // Ignore points of a contour touching the infill line from one side. + continue; + } + is.pos_p = p2.y(); is.pos_q = 1; } else { // First calculate the intersection parameter 't' as a rational number with non negative denominator. - if (p2(0) > p1(0)) { - is.pos_p = this_x - p1(0); - is.pos_q = p2(0) - p1(0); + if (p2.x() > p1.x()) { + is.pos_p = this_x - p1.x(); + is.pos_q = p2.x() - p1.x(); } else { - is.pos_p = p1(0) - this_x; - is.pos_q = p1(0) - p2(0); + is.pos_p = p1.x() - this_x; + is.pos_q = p1.x() - p2.x(); } - assert(is.pos_p >= 0 && is.pos_p <= is.pos_q); + assert(is.pos_q > 1); + assert(is.pos_p > 0 && is.pos_p < is.pos_q); // Make an intersection point from the 't'. - is.pos_p *= int64_t(p2(1) - p1(1)); - is.pos_p += p1(1) * int64_t(is.pos_q); + is.pos_p *= int64_t(p2.y() - p1.y()); + is.pos_p += p1.y() * int64_t(is.pos_q); } // +-1 to take rounding into account. - assert(is.pos() + 1 >= std::min(p1(1), p2(1))); - assert(is.pos() <= std::max(p1(1), p2(1)) + 1); + assert(is.pos() + 1 >= std::min(p1.y(), p2.y())); + assert(is.pos() <= std::max(p1.y(), p2.y()) + 1); segs[i].intersections.push_back(is); } } @@ -844,55 +855,46 @@ static std::vector slice_region_by_vertical_lines(con size_t j = 0; for (size_t i = 0; i < sil.intersections.size(); ++ i) { // What is the orientation of the segment at the intersection point? - size_t iContour = sil.intersections[i].iContour; - const Points &contour = poly_with_offset.contour(iContour).points; - size_t iSegment = sil.intersections[i].iSegment; - size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; - coord_t dir = contour[iSegment](0) - contour[iPrev](0); - bool low = dir > 0; - sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ? + SegmentIntersection &is = sil.intersections[i]; + const size_t iContour = is.iContour; + const Points &contour = poly_with_offset.contour(iContour).points; + const size_t iSegment = is.iSegment; + const size_t iPrev = prev_idx_modulo(iSegment, contour); + const coord_t dir = contour[iSegment].x() - contour[iPrev].x(); + const bool low = dir > 0; + is.type = poly_with_offset.is_contour_outer(iContour) ? (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) : (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH); - if (j > 0 && sil.intersections[i].iContour == sil.intersections[j-1].iContour) { - // Two successive intersection points on a vertical line with the same contour. This may be a special case. - if (sil.intersections[i].pos() == sil.intersections[j-1].pos()) { - // Two successive segments meet exactly at the vertical line. - #ifdef SLIC3R_DEBUG - // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. - size_t iSegment2 = sil.intersections[j-1].iSegment; - size_t iPrev2 = ((iSegment2 == 0) ? contour.size() : iSegment2) - 1; - assert(iSegment == iPrev2 || iSegment2 == iPrev); - #endif /* SLIC3R_DEBUG */ - if (sil.intersections[i].type == sil.intersections[j-1].type) { + bool take_next = true; + if (j > 0) { + SegmentIntersection &is2 = sil.intersections[j - 1]; + if (iContour == is2.iContour && is.pos_q == 1 && is2.pos_q == 1) { + // Two successive intersection points on a vertical line with the same contour, both points are end points of their respective contour segments. + if (is.pos_p == is2.pos_p) { + // Two successive segments meet exactly at the vertical line. + // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. + assert(iSegment == prev_idx_modulo(is2.iSegment, contour) || is2.iSegment == iPrev); + assert(is.type == is2.type); // Two successive segments of the same direction (both to the right or both to the left) // meet exactly at the vertical line. // Remove the second intersection point. - } else { - // This is a loop returning to the same point. - // It may as well be a vertex of a loop touching this vertical line. - // Remove both the lines. - -- j; + take_next = false; + } else if (is.type == is2.type) { + // Two non successive segments of the same direction (both to the right or both to the left) + // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment + // of the Z shaped path is aligned with this vertical line. + // Remove one of the intersection points while maximizing the vertical segment length. + if (low) { + // Remove the second intersection point, keep the first intersection point. + } else { + // Remove the first intersection point, keep the second intersection point. + sil.intersections[j-1] = sil.intersections[i]; + } + take_next = false; } - } else if (sil.intersections[i].type == sil.intersections[j-1].type) { - // Two non successive segments of the same direction (both to the right or both to the left) - // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment - // of the Z shaped path is aligned with this vertical line. - // Remove one of the intersection points while maximizing the vertical segment length. - if (low) { - // Remove the second intersection point, keep the first intersection point. - } else { - // Remove the first intersection point, keep the second intersection point. - sil.intersections[j-1] = sil.intersections[i]; - } - } else { - // Vertical line intersects a contour segment at a general position (not at one of its end points). - // or the contour just touches this vertical line with a vertical segment or a sequence of vertical segments. - // Keep both intersection points. - if (j < i) - sil.intersections[j] = sil.intersections[i]; - ++ j; } - } else { + } + if (take_next) { // Vertical line intersects a contour segment at a general position (not at one of its end points). if (j < i) sil.intersections[j] = sil.intersections[i]; @@ -905,7 +907,13 @@ static std::vector slice_region_by_vertical_lines(con } // Verify the segments. If something is wrong, give up. -#define ASSERT_THROW(CONDITION) do { assert(CONDITION); if (! (CONDITION)) throw InfillFailedException(); } while (0) +#ifdef INFILL_DEBUG_OUTPUT + #define INFILL_DEBUG_ASSERT(CONDITION) + try { +#else // INFILL_DEBUG_OUTPUT + #define INFILL_DEBUG_ASSERT(CONDITION) assert(CONDITION) +#endif // INFILL_DEBUG_OUTPUT +#define ASSERT_THROW(CONDITION) do { INFILL_DEBUG_ASSERT(CONDITION); if (! (CONDITION)) throw InfillFailedException(); } while (0) for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = segs[i_seg]; // The intersection points have to be even. @@ -925,6 +933,56 @@ static std::vector slice_region_by_vertical_lines(con } } #undef ASSERT_THROW +#undef INFILL_DEBUG_ASSERT +#ifdef INFILL_DEBUG_OUTPUT + } catch (const InfillFailedException & /* ex */) { + // Export the buggy result into an SVG file. + static int iRun = 0; + BoundingBox bbox = get_extents(poly_with_offset.polygons_src); + bbox.offset(scale_(3.)); + ::Slic3r::SVG svg(debug_out_path("slice_region_by_vertical_lines-failed-%d.svg", iRun ++), bbox); + svg.draw(poly_with_offset.polygons_src); + svg.draw_outline(poly_with_offset.polygons_src, "green"); + svg.draw_outline(poly_with_offset.polygons_outer, "green"); + svg.draw_outline(poly_with_offset.polygons_inner, "green"); + for (size_t i_seg = 0; i_seg < segs.size(); ++i_seg) { + SegmentedIntersectionLine &sil = segs[i_seg]; + for (size_t i = 0; i < sil.intersections.size();) { + // An intersection segment crossing the bigger contour may cross the inner offsetted contour even number of times. + if (sil.intersections[i].type != SegmentIntersection::OUTER_LOW) { + svg.draw(Point(sil.pos, sil.intersections[i].pos()), "red"); + break; + } + size_t j = i + 1; + if (j == sil.intersections.size()) { + svg.draw(Point(sil.pos, sil.intersections[i].pos()), "magenta"); + break; + } + if (! (sil.intersections[j].type == SegmentIntersection::INNER_LOW || sil.intersections[j].type == SegmentIntersection::OUTER_HIGH)) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "blue"); + break; + } + for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++j); + if (j == sil.intersections.size()) { + svg.draw(Point(sil.pos, sil.intersections[j - 1].pos()), "magenta"); + break; + } + if ((j & 1) != 1 || sil.intersections[j].type != SegmentIntersection::OUTER_HIGH) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "red"); + break; + } + if (! (i + 1 == j || sil.intersections[j - 1].type == SegmentIntersection::INNER_HIGH)) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "red"); + break; + } + svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[j].pos())), "black"); + i = j + 1; + } + } + assert(false); + throw; + } +#endif //INFILL_DEBUG_OUTPUT return segs; } @@ -2714,10 +2772,10 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. Point refpt = rotate_vector.second.rotated(- rotate_vector.first); - // _align_to_grid will not work correctly with positive pattern_shift. + // align_to_grid will not work correctly with positive pattern_shift. coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); - bounding_box.merge(_align_to_grid( + bounding_box.merge(align_to_grid( bounding_box.min, Point(line_spacing, line_spacing), refpt)); @@ -2825,6 +2883,45 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa return true; } +void make_fill_lines(const ExPolygonWithOffset &poly_with_offset, Point refpt, double angle, coord_t x_margin, coord_t line_spacing, coord_t pattern_shift, Polylines &fill_lines) +{ + BoundingBox bounding_box = poly_with_offset.bounding_box_src(); + // Don't produce infill lines, which fully overlap with the infill perimeter. + coord_t x_min = bounding_box.min.x() + x_margin; + coord_t x_max = bounding_box.max.x() - x_margin; + // extend bounding box so that our pattern will be aligned with other layers + // align_to_grid will not work correctly with positive pattern_shift. + coord_t pattern_shift_scaled = pattern_shift % line_spacing; + refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + bounding_box.merge(Slic3r::align_to_grid(bounding_box.min, Point(line_spacing, line_spacing), refpt)); + + // Intersect a set of euqally spaced vertical lines wiht expolygon. + // n_vlines = ceil(bbox_width / line_spacing) + const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing; + const double cos_a = cos(angle); + const double sin_a = sin(angle); + for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) + if (vline.pos >= x_min) { + if (vline.pos > x_max) + break; + for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { + auto it_low = it ++; + assert(it_low->type == SegmentIntersection::OUTER_LOW); + if (it_low->type != SegmentIntersection::OUTER_LOW) + continue; + auto it_high = it; + assert(it_high->type == SegmentIntersection::OUTER_HIGH); + if (it_high->type == SegmentIntersection::OUTER_HIGH) { + if (angle == 0.) + fill_lines.emplace_back(Point(vline.pos, it_low->pos()), Point(vline.pos, it_high->pos())); + else + fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); + ++ it; + } + } + } +} + bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out) { assert(sweep_params.size() > 1); @@ -2843,42 +2940,8 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar std::pair rotate_vector = this->_infill_direction(surface); for (const SweepParams &sweep : sweep_params) { // Rotate polygons so that we can work with vertical lines here - double angle = rotate_vector.first + sweep.angle_base; - ExPolygonWithOffset poly_with_offset(poly_with_offset_base, - angle); - BoundingBox bounding_box = poly_with_offset.bounding_box_src(); - // Don't produce infill lines, which fully overlap with the infill perimeter. - coord_t x_min = bounding_box.min.x() + line_width + coord_t(SCALED_EPSILON); - coord_t x_max = bounding_box.max.x() - line_width - coord_t(SCALED_EPSILON); - // extend bounding box so that our pattern will be aligned with other layers - // Transform the reference point to the rotated coordinate system. - Point refpt = rotate_vector.second.rotated(- angle); - // _align_to_grid will not work correctly with positive pattern_shift. - coord_t pattern_shift_scaled = coord_t(scale_(sweep.pattern_shift)) % line_spacing; - refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); - bounding_box.merge(_align_to_grid(bounding_box.min, Point(line_spacing, line_spacing), refpt)); - - // Intersect a set of euqally spaced vertical lines wiht expolygon. - // n_vlines = ceil(bbox_width / line_spacing) - const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing; - const double cos_a = cos(angle); - const double sin_a = sin(angle); - for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) - if (vline.pos > x_min) { - if (vline.pos >= x_max) - break; - for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { - auto it_low = it ++; - assert(it_low->type == SegmentIntersection::OUTER_LOW); - if (it_low->type != SegmentIntersection::OUTER_LOW) - continue; - auto it_high = it; - assert(it_high->type == SegmentIntersection::OUTER_HIGH); - if (it_high->type == SegmentIntersection::OUTER_HIGH) { - fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); - ++ it; - } - } - } + float angle = rotate_vector.first + sweep.angle_base; + make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, - angle), rotate_vector.second.rotated(-angle), angle, line_width + coord_t(SCALED_EPSILON), line_spacing, coord_t(scale_(sweep.pattern_shift)), fill_lines); } if (params.dont_connect() || fill_lines.size() <= 1) { @@ -2954,4 +3017,29 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ return polylines_out; } +Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + assert(! params.full_infill()); + + Polylines polylines_out; + std::pair rotate_vector = this->_infill_direction(surface); + ExPolygonWithOffset poly_with_offset(surface->expolygon, - rotate_vector.first, float(scale_(this->overlap - 0.5 * this->spacing))); + if (poly_with_offset.n_contours > 0) { + Polylines fill_lines; + coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); + // Create infill lines, keep them vertical. + make_fill_lines(poly_with_offset, rotate_vector.second.rotated(- rotate_vector.first), 0, 0, line_spacing, 0, fill_lines); + // Both the poly_with_offset and polylines_out are rotated, so the infill lines are strictly vertical. + connect_base_support(std::move(fill_lines), poly_with_offset.polygons_outer, poly_with_offset.bounding_box_outer(), polylines_out, this->spacing, params); + // Rotate back by rotate_vector.first + const double cos_a = cos(rotate_vector.first); + const double sin_a = sin(rotate_vector.first); + for (Polyline &pl : polylines_out) + for (Point &pt : pl.points) + pt.rotate(cos_a, sin_a); + } + return polylines_out; +} + } // namespace Slic3r + diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 692fba2bd1f..ad32ad20f34 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -97,6 +97,17 @@ protected: float _layer_angle(size_t idx) const override { return 0.f; } }; +class FillSupportBase : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillSupportBase(*this); } + ~FillSupportBase() override = default; + Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + +protected: + // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. + float _layer_angle(size_t idx) const override { return 0.f; } +}; } // namespace Slic3r diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 9d98ea6aebf..f6f4f56181d 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -299,20 +299,23 @@ bool liang_barsky_line_clipping( // Ugly named variant, that accepts the squared line // Don't call me with a nearly zero length vector! +// sympy: +// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[0]) +// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[1]) template int ray_circle_intersections_r2_lv2_c(T r2, T a, T b, T lv2, T c, std::pair, Eigen::Matrix> &out) { - T x0 = - a * c / lv2; - T y0 = - b * c / lv2; - T d = r2 - c * c / lv2; - if (d < T(0)) + T x0 = - a * c; + T y0 = - b * c; + T d2 = r2 * lv2 - c * c; + if (d2 < T(0)) return 0; - T mult = sqrt(d / lv2); - out.first.x() = x0 + b * mult; - out.first.y() = y0 - a * mult; - out.second.x() = x0 - b * mult; - out.second.y() = y0 + a * mult; - return mult == T(0) ? 1 : 2; + T d = sqrt(d2); + out.first.x() = (x0 + b * d) / lv2; + out.first.y() = (y0 - a * d) / lv2; + out.second.x() = (x0 - b * d) / lv2; + out.second.y() = (y0 + a * d) / lv2; + return d == T(0) ? 1 : 2; } template int ray_circle_intersections(T r, T a, T b, T c, std::pair, Eigen::Matrix> &out) diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index d5bac7249c1..d17142bb2ca 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -64,6 +64,7 @@ public: bool has_duplicate_points() const; // Remove exact duplicates, return true if any duplicate has been removed. bool remove_duplicate_points(); + void clear() { this->points.clear(); } void append(const Point &point) { this->points.push_back(point); } void append(const Points &src) { this->append(src.begin(), src.end()); } void append(const Points::const_iterator &begin, const Points::const_iterator &end) { this->points.insert(this->points.end(), begin, end); } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 84b4a825da7..f38a7066b10 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -413,6 +413,25 @@ unscaled(const Eigen::Matrix &v) noexcept return v.template cast() * SCALING_FACTOR; } +// Align a coordinate to a grid. The coordinate may be negative, +// the aligned value will never be bigger than the original one. +inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) { + // Current C++ standard defines the result of integer division to be rounded to zero, + // for both positive and negative numbers. Here we want to round down for negative + // numbers as well. + coord_t aligned = (coord < 0) ? + ((coord - spacing + 1) / spacing) * spacing : + (coord / spacing) * spacing; + assert(aligned <= coord); + return aligned; +} +inline Point align_to_grid(Point coord, Point spacing) + { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); } +inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) + { return base + align_to_grid(coord - base, spacing); } +inline Point align_to_grid(Point coord, Point spacing, Point base) + { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } + } // namespace Slic3r // start Boost diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ff433a48fd6..ce5bf1b2947 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -180,7 +180,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n } else if ( opt_key == "complete_objects" || opt_key == "filament_type" - || opt_key == "filament_soluble" || opt_key == "first_layer_temperature" || opt_key == "filament_loading_speed" || opt_key == "filament_loading_speed_start" @@ -213,6 +212,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "z_offset") { steps.emplace_back(psWipeTower); steps.emplace_back(psSkirt); + } else if (opt_key == "filament_soluble") { + steps.emplace_back(psWipeTower); + // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. + // Thus switching between soluble / non-soluble interface layer material may require recalculation of supports. + //FIXME Killing supports on any change of "filament_soluble" is rough. We should check for each object whether that is necessary. + osteps.emplace_back(posSupportMaterial); } else if ( opt_key == "first_layer_extrusion_width" || opt_key == "min_layer_height" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fd33f5807ce..08ab716d211 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1814,8 +1814,8 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Density of the first raft or support layer."); def->sidetext = L("%"); - def->min = 0; - def->max = 150; + def->min = 10; + def->max = 100; def->mode = comExpert; def->set_default_value(new ConfigOptionPercent(90)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 2ee6bc06131..7cfd515b0af 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -51,7 +51,7 @@ enum class FuzzySkinType { enum InfillPattern : int { ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, - ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount, + ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipCount, }; enum class IroningType { diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index bfd879e5238..2b1b5795ce5 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -1607,7 +1607,7 @@ static inline std::pairheight; bottom_z = (layer_id == 1) ? slicing_params.object_print_z_min : layer.lower_layer->lower_layer->print_z; } else { - print_z = layer.bottom_z() - slicing_params.gap_object_support; + print_z = layer.bottom_z() - slicing_params.gap_support_object; bottom_z = print_z; height = 0.; // Ignore this contact area if it's too low. @@ -3166,7 +3166,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); // Fill in the rest. fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); - if (no_sort) + if (no_sort && ! eec->empty()) dst.emplace_back(eec.release()); } } @@ -3174,8 +3174,13 @@ static inline void fill_expolygons_with_sheath_generate_paths( // Support layers, partially processed. struct MyLayerExtruded { - MyLayerExtruded() : layer(nullptr), m_polygons_to_extrude(nullptr) {} - ~MyLayerExtruded() { delete m_polygons_to_extrude; m_polygons_to_extrude = nullptr; } + MyLayerExtruded& operator=(MyLayerExtruded &&rhs) { + this->layer = rhs.layer; + this->extrusions = std::move(rhs.extrusions); + this->m_polygons_to_extrude = std::move(m_polygons_to_extrude); + rhs.layer = nullptr; + return *this; + } bool empty() const { return layer == nullptr || layer->polygons.empty(); @@ -3183,7 +3188,7 @@ struct MyLayerExtruded void set_polygons_to_extrude(Polygons &&polygons) { if (m_polygons_to_extrude == nullptr) - m_polygons_to_extrude = new Polygons(std::move(polygons)); + m_polygons_to_extrude = std::make_unique(std::move(polygons)); else *m_polygons_to_extrude = std::move(polygons); } @@ -3204,12 +3209,11 @@ struct MyLayerExtruded if (m_polygons_to_extrude == nullptr) { // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). assert(this->extrusions.empty()); - m_polygons_to_extrude = new Polygons(this->layer->polygons); + m_polygons_to_extrude = std::make_unique(this->layer->polygons); } Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); *m_polygons_to_extrude = union_(*m_polygons_to_extrude, true); - delete other.m_polygons_to_extrude; - other.m_polygons_to_extrude = nullptr; + other.m_polygons_to_extrude.reset(); } else if (m_polygons_to_extrude != nullptr) { assert(other.m_polygons_to_extrude == nullptr); // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). @@ -3232,12 +3236,14 @@ struct MyLayerExtruded } // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - PrintObjectSupportMaterial::MyLayer *layer; + PrintObjectSupportMaterial::MyLayer *layer { nullptr }; // Collect extrusions. They will be exported sorted by the bottom height. ExtrusionEntitiesPtr extrusions; + +private: // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. - Polygons *m_polygons_to_extrude; + std::unique_ptr m_polygons_to_extrude; }; typedef std::vector MyLayerExtrudedPtrs; @@ -3763,7 +3769,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipRectilinear); + InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipSupportBase); std::vector angles; angles.push_back(base_angle); @@ -3900,7 +3906,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); } }; - std::vector layer_caches(support_layers.size(), LayerCache()); + std::vector layer_caches(support_layers.size()); const auto fill_type_interface = @@ -4152,6 +4158,27 @@ void PrintObjectSupportMaterial::generate_toolpaths( } } }); + +#ifndef NDEBUG + struct Test { + static bool verify_nonempty(const ExtrusionEntityCollection *collection) { + for (const ExtrusionEntity *ee : collection->entities) { + if (const ExtrusionPath *path = dynamic_cast(ee)) + assert(! path->empty()); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) + assert(! multipath->empty()); + else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { + assert(! eecol->empty()); + return verify_nonempty(eecol); + } else + assert(false); + } + return true; + } + }; + for (const SupportLayer *support_layer : support_layers) + assert(Test::verify_nonempty(&support_layer->support_fills)); +#endif // NDEBUG } /* From 2e70270d644ec84c51d2ed7dca319104bd3aacb4 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 8 Apr 2021 16:36:52 +0200 Subject: [PATCH 046/154] Fix of brim under supports --- src/libslic3r/SupportMaterial.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 2b1b5795ce5..a343927921c 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -2898,9 +2898,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf //FIXME misusing contact_polygons for support columns. new_layer.contact_polygons = std::make_unique(columns); } - } else if (columns_base != nullptr) { + } else { + if (columns_base != nullptr) { // Expand the bases of the support columns in the 1st layer. - { Polygons &raft = columns_base->polygons; Polygons trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (inflate_factor_1st_layer > SCALED_EPSILON) { @@ -2911,11 +2911,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf raft = diff(offset(raft, step), trimming); } else raft = diff(raft, trimming); + if (contacts != nullptr) + columns_base->polygons = diff(columns_base->polygons, interface_polygons); } - if (contacts != nullptr) - columns_base->polygons = diff(columns_base->polygons, interface_polygons); if (! brim.empty()) { - columns_base->polygons = diff(columns_base->polygons, brim); + if (columns_base) + columns_base->polygons = diff(columns_base->polygons, brim); if (contacts) contacts->polygons = diff(contacts->polygons, brim); if (interfaces) From f8d7c7684a4391f6749759eada2f074b14cdfa34 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 9 Apr 2021 08:11:40 +0200 Subject: [PATCH 047/154] Configs should point to PrusaSlicer-alpha, not beta in alpha stage. --- src/slic3r/GUI/GUI_App.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b22cd6009b2..3023041f6dc 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -691,7 +691,7 @@ void GUI_App::init_app_config() { // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. // SetAppName(SLIC3R_APP_KEY); - SetAppName(SLIC3R_APP_KEY "-beta"); + SetAppName(SLIC3R_APP_KEY "-alpha"); // SetAppDisplayName(SLIC3R_APP_NAME); // Set the Slic3r data directory at the Slic3r XS module. From 25df2fb8f242cfaa1957d02d23cd1bb4ed455dcc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 9 Apr 2021 12:52:11 +0200 Subject: [PATCH 048/154] Do not convert custom gcode extrusion to travel --- src/libslic3r/GCode/GCodeProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7a179097144..d5be041f4c2 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2187,7 +2187,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } - if (type == EMoveType::Extrude && (m_extrusion_role == erCustom || m_width == 0.0f || m_height == 0.0f)) + if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) type = EMoveType::Travel; // time estimate section From b486dcc7f8d0fa6949f7b9d91872698092c39c69 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 10 Apr 2021 11:07:08 +0200 Subject: [PATCH 049/154] Do not use custom gcode in out of bed detection --- src/slic3r/GUI/GCodeViewer.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index eacf69c917b..67a489c7b6b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1714,7 +1714,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // for the gcode viewer we need to take in account all moves to correctly size the printbed m_paths_bounding_box.merge(move.position.cast()); else { - if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) + if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) m_paths_bounding_box.merge(move.position.cast()); } } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7bbdc72b115..0dea34eae49 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4996,8 +4996,9 @@ void GLCanvas3D::_render_background() const if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); else { - BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_paths_bounding_box()) : false; + const BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); + const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); + use_error_color &= (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) ? !test_volume.contains(paths_volume) : false; } } From aad9aa112dd777102dab3b7c0c8aab9c7c63bbef Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 12 Apr 2021 09:15:28 +0200 Subject: [PATCH 050/154] Fixed a typo in an error message (--sw_renderer -> --sw-renderer) --- src/slic3r/GUI/OpenGLManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index c843da46000..0b246171721 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -252,7 +252,7 @@ bool OpenGLManager::init_gl() message += _L("You may need to update your graphics card driver."); #ifdef _WIN32 message += "\n"; - message += _L("As a workaround, you may run PrusaSlicer with a software rendered 3D graphics by running prusa-slicer.exe with the --sw_renderer parameter."); + message += _L("As a workaround, you may run PrusaSlicer with a software rendered 3D graphics by running prusa-slicer.exe with the --sw-renderer parameter."); #endif wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Unsupported OpenGL version"), wxOK | wxICON_ERROR); } From 9207c11bc4a3e6f757da301764dad65cf1c24a34 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 12 Apr 2021 11:27:29 +0200 Subject: [PATCH 051/154] Bumped up version to 0.0.9 --- resources/profiles/Anycubic.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index 639d5df4753..44308abc891 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -5,7 +5,7 @@ name = Anycubic # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.8 +config_version = 0.0.9 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Anycubic/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% From 47af6eab9f042ac8e321227ec0de49d63c76ffef Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 12 Apr 2021 11:28:10 +0200 Subject: [PATCH 052/154] Update Anycubic.idx --- resources/profiles/Anycubic.idx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/profiles/Anycubic.idx b/resources/profiles/Anycubic.idx index b57fe3e660f..f3d115ca232 100644 --- a/resources/profiles/Anycubic.idx +++ b/resources/profiles/Anycubic.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.3.1 +0.0.9 Updated bed textures min_slic3r_version = 2.3.0-beta2 0.0.8 Updated start and end g-code for Anycubic Mega. 0.0.7 Updated start g-code for Anycubic Mega. From 906cad90b7a837de5078aa30d92bdf2995670a30 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 12 Apr 2021 11:40:55 +0200 Subject: [PATCH 053/154] updated min_slic3r_version --- resources/profiles/Artillery.idx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/Artillery.idx b/resources/profiles/Artillery.idx index 1fa68f82b59..b73e786a7f6 100644 --- a/resources/profiles/Artillery.idx +++ b/resources/profiles/Artillery.idx @@ -1,2 +1,2 @@ -min_slic3r_version = 2.4.0-alpha0 +min_slic3r_version = 2.3.1 0.0.1 Initial Artillery bundle From 69d9cf30624de89264d3e9d7a6e06c0af9a0f1c5 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 12 Apr 2021 11:42:32 +0200 Subject: [PATCH 054/154] version bumped up to 0.0.15 --- resources/profiles/Creality.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 6d977ade19d..0b4e4cf1403 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -5,7 +5,7 @@ name = Creality # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.14 +config_version = 0.0.15 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% From 3a03031fb572236e99a9f458c18747fa79089286 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 12 Apr 2021 11:44:14 +0200 Subject: [PATCH 055/154] Creality 0.0.15 --- resources/profiles/Creality.idx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx index 328ae4cf33b..34102212eb4 100644 --- a/resources/profiles/Creality.idx +++ b/resources/profiles/Creality.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.3.1 +0.0.15 Added new printer models, filament profiles. Various improvements. min_slic3r_version = 2.3.0-rc2 0.0.14 Optimized start g-code. Added filament profile. Various improvements. 0.0.13 Optimized start and end g-code. General improvements. From 114873d94645894f011dc803f2c8aae5439ce57f Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 12 Apr 2021 11:45:26 +0200 Subject: [PATCH 056/154] updated min_slic3r_version --- resources/profiles/INAT.idx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index 0d81f55a0f4..ea8d97b2819 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,3 +1,3 @@ -min_slic3r_version = 2.4.0-alpha0 +min_slic3r_version = 2.3.1 0.0.1 Initial version 0.0.2 Improved start gcode, changed filename format From 9ec9d83629b026ea1b10d6912ee666697869b21b Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 12 Apr 2021 12:11:46 +0200 Subject: [PATCH 057/154] Updated min_slic3r_version 2.3.1-beta --- resources/profiles/Anycubic.idx | 2 +- resources/profiles/Artillery.idx | 2 +- resources/profiles/Creality.idx | 2 +- resources/profiles/INAT.idx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/profiles/Anycubic.idx b/resources/profiles/Anycubic.idx index f3d115ca232..24a881f3034 100644 --- a/resources/profiles/Anycubic.idx +++ b/resources/profiles/Anycubic.idx @@ -1,4 +1,4 @@ -min_slic3r_version = 2.3.1 +min_slic3r_version = 2.3.1-beta 0.0.9 Updated bed textures min_slic3r_version = 2.3.0-beta2 0.0.8 Updated start and end g-code for Anycubic Mega. diff --git a/resources/profiles/Artillery.idx b/resources/profiles/Artillery.idx index b73e786a7f6..d1e3657728d 100644 --- a/resources/profiles/Artillery.idx +++ b/resources/profiles/Artillery.idx @@ -1,2 +1,2 @@ -min_slic3r_version = 2.3.1 +min_slic3r_version = 2.3.1-beta 0.0.1 Initial Artillery bundle diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx index 34102212eb4..2833b8afbb4 100644 --- a/resources/profiles/Creality.idx +++ b/resources/profiles/Creality.idx @@ -1,4 +1,4 @@ -min_slic3r_version = 2.3.1 +min_slic3r_version = 2.3.1-beta 0.0.15 Added new printer models, filament profiles. Various improvements. min_slic3r_version = 2.3.0-rc2 0.0.14 Optimized start g-code. Added filament profile. Various improvements. diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index ea8d97b2819..47632c29a00 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,3 +1,3 @@ -min_slic3r_version = 2.3.1 +min_slic3r_version = 2.3.1-beta 0.0.1 Initial version 0.0.2 Improved start gcode, changed filename format From 05386e9b20f4349f3eb6a845528cc4da333333e3 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 12 Apr 2021 14:56:30 +0200 Subject: [PATCH 058/154] FDM snug supports: New parameter "closing radius", inspired by Cura's support_join_distance --- src/libslic3r/Config.hpp | 3 ++- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 10 ++++++++++ src/libslic3r/PrintConfig.hpp | 3 +++ src/libslic3r/PrintObject.cpp | 1 + src/libslic3r/SupportMaterial.cpp | 16 ++++++++++++---- src/slic3r/GUI/ConfigManipulation.cpp | 2 ++ src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 80d34e8216c..9aab435edd2 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1967,8 +1967,9 @@ public: int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. + // Thus the virtual method getInt() is used to retrieve the enum value. template - ENUM opt_enum(const t_config_option_key &opt_key) const { return this->option>(opt_key)->value; } + ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d57bcb4516a..7db61a20f13 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -427,7 +427,7 @@ const std::vector& Preset::print_options() "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", "min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", - "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_style", + "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", "support_material_bottom_contact_distance", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 08ab716d211..5516b298d31 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2366,6 +2366,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(-1)); + def = this->add("support_material_closing_radius", coFloat); + def->label = L("Closing radius"); + def->category = L("Support material"); + def->tooltip = L("For snug supports, the support regions will be merged using morphological closing operation." + " Gaps smaller than the closing radius will be filled in."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2)); + def = this->add("support_material_interface_spacing", coFloat); def->label = L("Interface pattern spacing"); def->category = L("Support material"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 7cfd515b0af..aab5096624c 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -530,6 +530,8 @@ public: ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionEnum support_material_pattern; ConfigOptionEnum support_material_interface_pattern; + // Morphological closing of support areas. Only used for "sung" supports. + ConfigOptionFloat support_material_closing_radius; // Spacing between support material lines (the hatching distance). ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; @@ -579,6 +581,7 @@ protected: OPT_PTR(support_material_interface_extruder); OPT_PTR(support_material_interface_layers); OPT_PTR(support_material_bottom_interface_layers); + OPT_PTR(support_material_closing_radius); OPT_PTR(support_material_interface_spacing); OPT_PTR(support_material_interface_speed); OPT_PTR(support_material_pattern); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1989b18846d..cbf3e71ab73 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -591,6 +591,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_material_style" || opt_key == "support_material_xy_spacing" || opt_key == "support_material_spacing" + || opt_key == "support_material_closing_radius" || opt_key == "support_material_synchronize_layers" || opt_key == "support_material_threshold" || opt_key == "support_material_with_sheath" diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index a343927921c..1242df1ea5d 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -672,6 +672,7 @@ struct SupportGridParams { grid_resolution(object_config.support_material_spacing.value + support_material_flow.spacing()), support_angle(Geometry::deg2rad(object_config.support_material_angle.value)), extrusion_width(support_material_flow.spacing()), + support_material_closing_radius(object_config.support_material_closing_radius.value), expansion_to_slice(coord_t(support_material_flow.scaled_spacing() / 2 + 5)), expansion_to_propagate(-3) {} @@ -679,6 +680,7 @@ struct SupportGridParams { double grid_resolution; double support_angle; double extrusion_width; + double support_material_closing_radius; coord_t expansion_to_slice; coord_t expansion_to_propagate; }; @@ -694,7 +696,9 @@ public: const SupportGridParams ¶ms) : m_style(params.style), m_support_polygons(support_polygons), m_trimming_polygons(trimming_polygons), - m_support_spacing(params.grid_resolution), m_support_angle(params.support_angle) + m_support_spacing(params.grid_resolution), m_support_angle(params.support_angle), + m_extrusion_width(params.extrusion_width), + m_support_material_closing_radius(params.support_material_closing_radius) { switch (m_style) { case smsGrid: @@ -878,9 +882,10 @@ public: return out; } case smsSnug: - // Just close the gaps. - float thr = scaled(0.5); - return smooth_outward(offset(offset_ex(*m_support_polygons, thr), - thr), thr); + // Merge the support polygons by applying morphological closing and inwards smoothing. + auto closing_distance = scaled(m_support_material_closing_radius); + auto smoothing_distance = scaled(m_extrusion_width); + return smooth_outward(offset(offset_ex(*m_support_polygons, closing_distance), - closing_distance), smoothing_distance); } assert(false); return Polygons(); @@ -1128,6 +1133,9 @@ private: coordf_t m_support_angle; // X spacing of the support lines parallel with the Y axis. coordf_t m_support_spacing; + coordf_t m_extrusion_width; + // For snug supports: Morphological closing of support areas. + coordf_t m_support_material_closing_radius; #ifdef SUPPORT_USE_AGG_RASTERIZER Vec2i m_grid_size; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 9334eea4719..cd7805a8808 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -278,6 +278,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_support_material_auto = have_support_material && config->opt_bool("support_material_auto"); bool have_support_interface = config->opt_int("support_material_interface_layers") > 0; bool have_support_soluble = have_support_material && config->opt_float("support_material_contact_distance") == 0; + auto support_material_style = config->opt_enum("support_material_style"); for (auto el : { "support_material_style", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_angle", "support_material_interface_pattern", "support_material_interface_layers", @@ -286,6 +287,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field(el, have_support_material); toggle_field("support_material_threshold", have_support_material_auto); toggle_field("support_material_bottom_contact_distance", have_support_material && ! have_support_soluble); + toggle_field("support_material_closing_radius", have_support_material && support_material_style == smsSnug); for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", "support_material_interface_speed", "support_material_interface_contact_loops" }) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index cfd36e6878c..0e954a90645 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1515,6 +1515,7 @@ void TabPrint::build() optgroup->append_single_option_line("support_material_with_sheath", category_path + "with-sheath-around-the-support"); optgroup->append_single_option_line("support_material_spacing", category_path + "pattern-spacing-0-inf"); optgroup->append_single_option_line("support_material_angle", category_path + "pattern-angle"); + optgroup->append_single_option_line("support_material_closing_radius", category_path + "pattern-angle"); optgroup->append_single_option_line("support_material_interface_layers", category_path + "interface-layers"); optgroup->append_single_option_line("support_material_bottom_interface_layers", category_path + "interface-layers"); optgroup->append_single_option_line("support_material_interface_pattern", category_path + "interface-pattern"); From 1b6534d5ac7f35ff795bea114cecbbc6e7c35a1a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 13 Apr 2021 11:31:54 +0200 Subject: [PATCH 059/154] Clipper / ClipperUtils: 1) Let Clipper use int32_t for representing its coordinates. This reduces memory and allows to skip conversion between Slic3r Polygon and Clipper polygon. 2) Disable additional offset before executing the Clipper Offset algorithm. We don't see any reason for that and it required 64bit Clipper coordinates, which were disabled with 1). --- src/clipper/clipper.cpp | 52 +++++++++++++-------- src/clipper/clipper.hpp | 31 ++++++++++--- src/libslic3r/ClipperUtils.cpp | 85 ++++++++++++++++++++++++++++++++-- src/libslic3r/ClipperUtils.hpp | 26 ++++++----- src/libslic3r/Geometry.cpp | 2 + 5 files changed, 155 insertions(+), 41 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 3c0057b22bc..cbe54a0647b 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -287,6 +287,11 @@ bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) } //---------------------------------------------------------------------- +#ifdef CLIPPERLIB_INT32 +inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cInt dy2, bool /* UseFullInt64Range */) { + return int64_t(dy1) * int64_t(dx2) == int64_t(dx1) * int64_t(dy2); +} +#else inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cInt dy2, bool UseFullInt64Range) { return (UseFullInt64Range) ? // |dx1| < 2^63, |dx2| < 2^63 etc, @@ -296,6 +301,8 @@ inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cI // therefore the following computation could be done with 64bit arithmetics. dy1 * dx2 == dx1 * dy2; } +#endif + inline bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { return SlopesEqual(e1.Delta.X, e1.Delta.Y, e2.Delta.X, e2.Delta.Y, UseFullInt64Range); } inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3, bool UseFullInt64Range) @@ -363,8 +370,8 @@ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) } else { - b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; - b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + b1 = double(Edge1.Bot.X) - double(Edge1.Bot.Y) * Edge1.Dx; + b2 = double(Edge2.Bot.X) - double(Edge2.Bot.Y) * Edge2.Dx; double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); ip.Y = Round(q); ip.X = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ? @@ -569,6 +576,7 @@ bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) // ClipperBase class methods ... //------------------------------------------------------------------------------ +#ifndef CLIPPERLIB_INT32 // Called from ClipperBase::AddPath() to verify the scale of the input polygon coordinates. inline void RangeTest(const IntPoint& Pt, bool& useFullRange) { @@ -583,6 +591,7 @@ inline void RangeTest(const IntPoint& Pt, bool& useFullRange) RangeTest(Pt, useFullRange); } } +#endif // CLIPPERLIB_INT32 //------------------------------------------------------------------------------ // Called from ClipperBase::AddPath() to construct the Local Minima List. @@ -805,13 +814,17 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b try { edges[1].Curr = pg[1]; +#ifndef CLIPPERLIB_INT32 RangeTest(pg[0], m_UseFullRange); RangeTest(pg[highI], m_UseFullRange); +#endif // CLIPPERLIB_INT32 InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); for (int i = highI - 1; i >= 1; --i) { +#ifndef CLIPPERLIB_INT32 RangeTest(pg[i], m_UseFullRange); +#endif // CLIPPERLIB_INT32 InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); } } @@ -967,7 +980,9 @@ void ClipperBase::Clear() CLIPPERLIB_PROFILE_FUNC(); m_MinimaList.clear(); m_edges.clear(); +#ifndef CLIPPERLIB_INT32 m_UseFullRange = false; +#endif // CLIPPERLIB_INT32 m_HasOpenPaths = false; } //------------------------------------------------------------------------------ @@ -3322,9 +3337,9 @@ DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) if(pt2.X == pt1.X && pt2.Y == pt1.Y) return DoublePoint(0, 0); - double Dx = (double)(pt2.X - pt1.X); - double dy = (double)(pt2.Y - pt1.Y); - double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + double Dx = double(pt2.X - pt1.X); + double dy = double(pt2.Y - pt1.Y); + double f = 1.0 / std::sqrt( Dx*Dx + dy*dy ); Dx *= f; dy *= f; return DoublePoint(dy, -Dx); @@ -3530,8 +3545,9 @@ void ClipperOffset::DoOffset(double delta) } //see offset_triginometry3.svg in the documentation folder ... - if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); - else m_miterLim = 0.5; + m_miterLim = (MiterLimit > 2) ? + 2. / (MiterLimit * MiterLimit) : + 0.5; double y; if (ArcTolerance <= 0.0) y = def_arc_tolerance; @@ -3633,11 +3649,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { int j = len - 1; - pt1 = IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * - delta), Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * delta), Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[j].X - m_normals[j].X * - delta), Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[j].X - m_normals[j].X * delta), Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); m_destPoly.push_back(pt1); } else @@ -3662,11 +3676,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { - pt1 = IntPoint(Round(m_srcPoly[0].X - m_normals[0].X * delta), - Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[0].X - m_normals[0].X * delta), Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[0].X + m_normals[0].X * delta), - Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[0].X + m_normals[0].X * delta), Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); m_destPoly.push_back(pt1); } else @@ -3753,7 +3765,7 @@ void ClipperOffset::DoRound(int j, int k) { double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); - int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); + auto steps = std::max(Round(m_StepsPerRad * std::fabs(a)), 1); double X = m_normals[k].X, Y = m_normals[k].Y, X2; for (int i = 0; i < steps; ++i) @@ -3885,8 +3897,8 @@ void SimplifyPolygons(Paths &polys, PolyFillType fillType) inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) { - double Dx = ((double)pt1.X - pt2.X); - double dy = ((double)pt1.Y - pt2.Y); + auto Dx = double(pt1.X - pt2.X); + auto dy = double(pt1.Y - pt2.Y); return (Dx*Dx + dy*dy); } //------------------------------------------------------------------------------ @@ -3937,8 +3949,8 @@ bool SlopesNearCollinear(const IntPoint& pt1, bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) { - double Dx = (double)pt1.X - pt2.X; - double dy = (double)pt1.Y - pt2.Y; + auto Dx = double(pt1.X - pt2.X); + auto dy = double(pt1.Y - pt2.Y); return ((Dx * Dx) + (dy * dy) <= distSqrd); } //------------------------------------------------------------------------------ diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 8b34779e3af..48e83d04610 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -71,12 +71,22 @@ enum PolyType { ptSubject, ptClip }; //see http://glprogramming.com/red/chapter11.html enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; +// If defined, Clipper will work with 32bit signed int coordinates to reduce memory +// consumption and to speed up exact orientation predicate calculation. +// In that case, coordinates and their differences (vectors of the coordinates) have to fit int32_t. +#define CLIPPERLIB_INT32 + // Point coordinate type -typedef int64_t cInt; -// Maximum cInt value to allow a cross product calculation using 32bit expressions. -static cInt const loRange = 0x3FFFFFFF; -// Maximum allowed cInt value. -static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; +#ifdef CLIPPERLIB_INT32 + // Coordinates and their differences (vectors of the coordinates) have to fit int32_t. + typedef int32_t cInt; +#else + typedef int64_t cInt; + // Maximum cInt value to allow a cross product calculation using 32bit expressions. + static constexpr cInt const loRange = 0x3FFFFFFF; // 0x3FFFFFFF = 1 073 741 823 + // Maximum allowed cInt value. + static constexpr cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; +#endif // CLIPPERLIB_INT32 struct IntPoint { cInt X; @@ -289,7 +299,11 @@ enum EdgeSide { esLeft = 1, esRight = 2}; class ClipperBase { public: - ClipperBase() : m_UseFullRange(false), m_HasOpenPaths(false) {} + ClipperBase() : +#ifndef CLIPPERLIB_INT32 + m_UseFullRange(false), +#endif // CLIPPERLIB_INT32 + m_HasOpenPaths(false) {} ~ClipperBase() { Clear(); } bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); @@ -310,9 +324,14 @@ protected: // Local minima (Y, left edge, right edge) sorted by ascending Y. std::vector m_MinimaList; +#ifdef CLIPPERLIB_INT32 + static constexpr const bool m_UseFullRange = false; +#else // CLIPPERLIB_INT32 // True if the input polygons have abs values higher than loRange, but lower than hiRange. // False if the input polygons have abs values lower or equal to loRange. bool m_UseFullRange; +#endif // CLIPPERLIB_INT32 + // A vector of edges per each input path. std::vector> m_edges; // Don't remove intermediate vertices of a collinear sequence of points. diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 305ea134f09..cd243dfb1ba 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -57,6 +57,7 @@ err: } #endif /* CLIPPER_UTILS_DEBUG */ +#ifdef CLIPPERUTILS_OFFSET_SCALE void scaleClipperPolygon(ClipperLib::Path &polygon) { CLIPPERUTILS_PROFILE_FUNC(); @@ -98,6 +99,7 @@ void unscaleClipperPolygons(ClipperLib::Paths &polygons) pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; } } +#endif // CLIPPERUTILS_OFFSET_SCALE //----------------------------------------------------------- // legacy code from Clipper documentation @@ -222,8 +224,10 @@ ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input) ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { +#ifdef CLIPPERUTILS_OFFSET_SCALE // scale input scaleClipperPolygons(input); +#endif // CLIPPERUTILS_OFFSET_SCALE // perform offset ClipperLib::ClipperOffset co; @@ -231,14 +235,20 @@ ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; +#ifdef CLIPPERUTILS_OFFSET_SCALE float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta; +#endif // CLIPPERUTILS_OFFSET_SCALE co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPaths(input, joinType, endType); ClipperLib::Paths retval; co.Execute(retval, delta_scaled); +#ifdef CLIPPERUTILS_OFFSET_SCALE // unscale output unscaleClipperPolygons(retval); +#endif // CLIPPERUTILS_OFFSET_SCALE return retval; } @@ -257,14 +267,24 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, { // printf("new ExPolygon offset\n"); // 1) Offset the outer contour. - const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#ifdef CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta; +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::Paths contours; { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(expolygon.contour); +#ifdef CLIPPERUTILS_OFFSET_SCALE scaleClipperPolygon(input); +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) +#ifdef CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + co.ArcTolerance = miterLimit; +#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); @@ -278,17 +298,23 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, holes.reserve(expolygon.holes.size()); for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); +#ifdef CLIPPERUTILS_OFFSET_SCALE scaleClipperPolygon(input); +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) +#ifdef CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + co.ArcTolerance = miterLimit; +#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPath(input, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths out; co.Execute(out, - delta_scaled); - holes.insert(holes.end(), out.begin(), out.end()); + append(holes, std::move(out)); } } @@ -305,7 +331,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, } // 4) Unscale the output. +#ifdef CLIPPERUTILS_OFFSET_SCALE unscaleClipperPolygons(output); +#endif // CLIPPERUTILS_OFFSET_SCALE return output; } @@ -315,7 +343,11 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { - const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#ifdef CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + float delta_scaled = delta; +#endif // CLIPPERUTILS_OFFSET_SCALE // Offsetted ExPolygons before they are united. ClipperLib::Paths contours_cummulative; contours_cummulative.reserve(expolygons.size()); @@ -327,10 +359,16 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt ClipperLib::Paths contours; { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(it_expoly->contour); +#ifdef CLIPPERUTILS_OFFSET_SCALE scaleClipperPolygon(input); +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) +#ifdef CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + co.ArcTolerance = miterLimit; +#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); @@ -351,10 +389,16 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt { for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); +#ifdef CLIPPERUTILS_OFFSET_SCALE scaleClipperPolygon(input); +#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) +#ifdef CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + co.ArcTolerance = miterLimit; +#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); @@ -413,8 +457,10 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt output = std::move(contours_cummulative); } +#ifdef CLIPPERUTILS_OFFSET_SCALE // 4) Unscale the output. unscaleClipperPolygons(output); +#endif // CLIPPERUTILS_OFFSET_SCALE return output; } @@ -425,8 +471,10 @@ _offset2(const Polygons &polygons, const float delta1, const float delta2, // read input ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons); +#ifdef CLIPPERUTILS_OFFSET_SCALE // scale input scaleClipperPolygons(input); +#endif // CLIPPERUTILS_OFFSET_SCALE // prepare ClipperOffset object ClipperLib::ClipperOffset co; @@ -435,8 +483,13 @@ _offset2(const Polygons &polygons, const float delta1, const float delta2, } else { co.MiterLimit = miterLimit; } +#ifdef CLIPPERUTILS_OFFSET_SCALE float delta_scaled1 = delta1 * float(CLIPPER_OFFSET_SCALE); float delta_scaled2 = delta2 * float(CLIPPER_OFFSET_SCALE); +#else // CLIPPERUTILS_OFFSET_SCALE + float delta_scaled1 = delta1; + float delta_scaled2 = delta2; +#endif // CLIPPERUTILS_OFFSET_SCALE co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR); // perform first offset @@ -450,8 +503,10 @@ _offset2(const Polygons &polygons, const float delta1, const float delta2, ClipperLib::Paths retval; co.Execute(retval, delta_scaled2); +#ifdef CLIPPERUTILS_OFFSET_SCALE // unscale output unscaleClipperPolygons(retval); +#endif // CLIPPERUTILS_OFFSET_SCALE return retval; } @@ -789,8 +844,10 @@ void safety_offset(ClipperLib::Paths* paths) { CLIPPERUTILS_PROFILE_FUNC(); +#ifdef CLIPPERUTILS_OFFSET_SCALE // scale input scaleClipperPolygons(*paths); +#endif // CLIPPERUTILS_OFFSET_SCALE // perform offset (delta = scale 1e-05) ClipperLib::ClipperOffset co; @@ -816,7 +873,11 @@ void safety_offset(ClipperLib::Paths* paths) CLIPPERUTILS_PROFILE_BLOCK(safety_offset_Execute); // offset outside by 10um ClipperLib::Paths out_this; +#ifdef CLIPPERUTILS_OFFSET_SCALE co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE)); +#else // CLIPPERUTILS_OFFSET_SCALE + co.Execute(out_this, ccw ? 10.f : -10.f); +#endif // CLIPPERUTILS_OFFSET_SCALE if (! ccw) { // Reverse the resulting contours once again. for (ClipperLib::Paths::iterator it = out_this.begin(); it != out_this.end(); ++ it) @@ -830,8 +891,10 @@ void safety_offset(ClipperLib::Paths* paths) } *paths = std::move(out); +#ifdef CLIPPERUTILS_OFFSET_SCALE // unscale output unscaleClipperPolygons(*paths); +#endif // CLIPPERUTILS_OFFSET_SCALE } Polygons top_level_islands(const Slic3r::Polygons &polygons) @@ -925,8 +988,10 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v // Add a new point to the output, scale by CLIPPER_OFFSET_SCALE and round to ClipperLib::cInt. auto add_offset_point = [&out](Vec2d pt) { +#ifdef CLIPPERUTILS_OFFSET_SCALE pt *= double(CLIPPER_OFFSET_SCALE); - pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0)); +#endif // CLIPPERUTILS_OFFSET_SCALE + pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0)); out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y())); }; @@ -1075,8 +1140,10 @@ Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector& ds : deltas) clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } +#ifdef CLIPPERUTILS_OFFSET_SCALE // 4) Unscale the output. unscaleClipperPolygons(output); +#endif // CLIPPERUTILS_OFFSET_SCALE return ClipperPaths_to_Slic3rPolygons(output); } @@ -1152,7 +1221,9 @@ for (const std::vector& ds : deltas) #endif /* NDEBUG */ // 3) Subtract holes from the contours. +#ifdef CLIPPERUTILS_OFFSET_SCALE unscaleClipperPolygons(contours); +#endif // CLIPPERUTILS_OFFSET_SCALE ExPolygons output; if (holes.empty()) { output.reserve(contours.size()); @@ -1160,7 +1231,9 @@ for (const std::vector& ds : deltas) output.emplace_back(ClipperPath_to_Slic3rPolygon(path)); } else { ClipperLib::Clipper clipper; +#ifdef CLIPPERUTILS_OFFSET_SCALE unscaleClipperPolygons(holes); +#endif // CLIPPERUTILS_OFFSET_SCALE clipper.AddPaths(contours, ClipperLib::ptSubject, true); clipper.AddPaths(holes, ClipperLib::ptClip, true); ClipperLib::PolyTree polytree; @@ -1200,7 +1273,9 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vectorvertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex0()->y()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex1()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex1()->y()) > double(CLIPPER_MAX_COORD_UNSCALED)) return false; +#endif // CLIPPERLIB_INT32 // construct the line representing this edge of the Voronoi diagram const Line line( From 1f1540533627241973c98666aafc2f4e3f2c666f Mon Sep 17 00:00:00 2001 From: cp Date: Mon, 12 Apr 2021 19:52:25 +0200 Subject: [PATCH 060/154] Fix boost dependency url. Original host's lifetime has ended, see here: https://github.com/boostorg/boost/issues/502 This is PR #6349, amended by @lukasmatena who added the changes for platforms other than Windows. --- deps/deps-linux.cmake | 4 ++-- deps/deps-macos.cmake | 4 ++-- deps/deps-mingw.cmake | 5 +++-- deps/deps-windows.cmake | 5 +++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/deps/deps-linux.cmake b/deps/deps-linux.cmake index 35522504cab..c81d6785dbb 100644 --- a/deps/deps-linux.cmake +++ b/deps/deps-linux.cmake @@ -13,7 +13,7 @@ include("deps-unix-common.cmake") ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.gz" + URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" URL_HASH SHA256=aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ./bootstrap.sh @@ -100,5 +100,5 @@ ExternalProject_Add(dep_libcurl BUILD_COMMAND make "-j${NPROC}" INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" ) - add_dependencies(dep_openvdb dep_boost) + diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index 53ba008c3e5..de77dafa807 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -18,7 +18,7 @@ include("deps-unix-common.cmake") ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.gz" + URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" URL_HASH SHA256=aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ./bootstrap.sh @@ -87,5 +87,5 @@ ExternalProject_Add(dep_libcurl BUILD_COMMAND make "-j${NPROC}" INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" ) +add_dependencies(dep_openvdb dep_boost) -add_dependencies(dep_openvdb dep_boost) \ No newline at end of file diff --git a/deps/deps-mingw.cmake b/deps/deps-mingw.cmake index c97346bb039..cae7fc3719c 100644 --- a/deps/deps-mingw.cmake +++ b/deps/deps-mingw.cmake @@ -9,7 +9,7 @@ include("deps-unix-common.cmake") ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.gz" + URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" URL_HASH SHA256=aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a BUILD_IN_SOURCE 1 CONFIGURE_COMMAND bootstrap.bat @@ -58,4 +58,5 @@ ExternalProject_Add(dep_libcurl -DCURL_DISABLE_GOPHER=ON -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ${DEP_CMAKE_OPTS} -) \ No newline at end of file +) + diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index c0d80bb291d..fc9f55f5f47 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -55,7 +55,7 @@ endmacro() ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.gz" + URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" URL_HASH SHA256=aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a BUILD_IN_SOURCE 1 CONFIGURE_COMMAND bootstrap.bat @@ -295,4 +295,5 @@ if (${DEP_DEBUG}) COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj WORKING_DIRECTORY "${BINARY_DIR}" ) -endif () \ No newline at end of file +endif () + From a1a50fe096327a5992eed105ed422a7747597683 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 13 Apr 2021 13:28:21 +0200 Subject: [PATCH 061/154] Marked the unsafe ClipperUtils offset functions with CLIPPERUTILS_UNSAFE_OFFSET Replaced some of the unsafe offset functions with safe variants. Please test the 1) print bed from STL function 2) concentric infill --- src/libslic3r/BridgeDetector.cpp | 2 +- src/libslic3r/ClipperUtils.hpp | 13 +++++++++++++ src/libslic3r/ExPolygon.hpp | 22 ++++++++++++++++++++++ src/libslic3r/Fill/FillConcentric.cpp | 8 ++++---- src/libslic3r/Fill/FillLine.cpp | 2 +- src/libslic3r/LayerRegion.cpp | 2 +- src/libslic3r/Polygon.hpp | 4 +++- src/libslic3r/TriangleMesh.cpp | 18 ++++++++++-------- 8 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index ff33e81d532..671ebbdaad0 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -40,7 +40,7 @@ void BridgeDetector::initialize() this->angle = -1.; // Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors. - Polygons grown = offset(to_polygons(this->expolygons), float(this->spacing)); + Polygons grown = offset(this->expolygons, float(this->spacing)); // Detect possible anchoring edges of this bridging region. // Detect what edges lie on lower slices by turning bridge contour and holes diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index c1b8875831d..6668a9ae9bd 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -12,6 +12,8 @@ using ClipperLib::jtMiter; using ClipperLib::jtRound; using ClipperLib::jtSquare; +#define CLIPPERUTILS_UNSAFE_OFFSET + // #define CLIPPERUTILS_OFFSET_SCALE #ifdef CLIPPERUTILS_OFFSET_SCALE @@ -51,8 +53,11 @@ ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit); inline Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } + +#ifdef CLIPPERUTILS_UNSAFE_OFFSET inline Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +#endif // CLIPPERUTILS_UNSAFE_OFFSET // offset Polylines inline Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3) @@ -69,13 +74,18 @@ inline Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float { return ClipperPaths_to_Slic3rPolygons(_offset(expolygons, delta, joinType, miterLimit)); } inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } + +#ifdef CLIPPERUTILS_UNSAFE_OFFSET inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +#endif // CLIPPERUTILS_UNSAFE_OFFSET + inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); } inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); } +#ifdef CLIPPERUTILS_UNSAFE_OFFSET ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); @@ -85,6 +95,8 @@ Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +#endif // CLIPPERUTILS_UNSAFE_OFFSET + Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); @@ -323,6 +335,7 @@ void safety_offset(ClipperLib::Paths* paths); Polygons top_level_islands(const Slic3r::Polygons &polygons); +ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector &deltas, double miter_limit); Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 46b3a3a1b9d..73770bb185a 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -217,6 +217,28 @@ inline Polygons to_polygons(const ExPolygons &src) return polygons; } +inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygon &src) +{ + ConstPolygonPtrs polygons; + polygons.reserve(src.holes.size() + 1); + polygons.emplace_back(&src.contour); + for (const Polygon &hole : src.holes) + polygons.emplace_back(&hole); + return polygons; +} + +inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygons &src) +{ + ConstPolygonPtrs polygons; + polygons.reserve(number_polygons(src)); + for (const ExPolygon &expoly : src) { + polygons.emplace_back(&expoly.contour); + for (const Polygon &hole : expoly.holes) + polygons.emplace_back(&hole); + } + return polygons; +} + inline Polygons to_polygons(ExPolygon &&src) { Polygons polygons; diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 1b96c43a4d5..785c93be3b3 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -24,11 +24,11 @@ void FillConcentric::_fill_surface_single( this->spacing = unscale(distance); } - Polygons loops = to_polygons(std::move(expolygon)); - Polygons last = loops; + Polygons loops = to_polygons(expolygon); + ExPolygons last { std::move(expolygon) }; while (! last.empty()) { - last = offset2(last, -(distance + min_spacing/2), +min_spacing/2); - append(loops, last); + last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2); + append(loops, to_polygons(last)); } // generate paths from the outermost to the innermost, to avoid diff --git a/src/libslic3r/Fill/FillLine.cpp b/src/libslic3r/Fill/FillLine.cpp index 6a0a19efd20..f6431a33334 100644 --- a/src/libslic3r/Fill/FillLine.cpp +++ b/src/libslic3r/Fill/FillLine.cpp @@ -58,7 +58,7 @@ void FillLine::_fill_surface_single( pts.push_back(it->a); pts.push_back(it->b); } - Polylines polylines = intersection_pl(polylines_src, offset(to_polygons(expolygon), scale_(0.02)), false); + Polylines polylines = intersection_pl(polylines_src, offset(expolygon, scale_(0.02)), false); // FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines! const float INFILL_OVERLAP_OVER_SPACING = 0.3f; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index f6c0c9c7a55..059d94e25be 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -216,7 +216,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly break; } // Grown by 3mm. - Polygons polys = offset(to_polygons(bridges[i].expolygon), margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); + Polygons polys = offset(bridges[i].expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); if (idx_island == -1) { BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; } else { diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index aa72b4244d8..dd2d68d46fe 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -11,7 +11,9 @@ namespace Slic3r { class Polygon; -typedef std::vector Polygons; +using Polygons = std::vector; +using PolygonPtrs = std::vector; +using ConstPolygonPtrs = std::vector; class Polygon : public MultiPoint { diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index d5a34908779..f8fa1ca17f1 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -511,20 +511,22 @@ void TriangleMesh::merge(const TriangleMesh &mesh) //FIXME This could be extremely slow! Use it for tiny meshes only! ExPolygons TriangleMesh::horizontal_projection() const { - Polygons pp; - pp.reserve(this->stl.stats.number_of_facets); + ClipperLib::Paths paths; + Polygon p; + p.points.assign(3, Point()); + auto delta = scaled(0.01); + std::vector deltas { delta, delta, delta }; + paths.reserve(this->stl.stats.number_of_facets); for (const stl_facet &facet : this->stl.facet_start) { - Polygon p; - p.points.resize(3); p.points[0] = Point::new_scale(facet.vertex[0](0), facet.vertex[0](1)); p.points[1] = Point::new_scale(facet.vertex[1](0), facet.vertex[1](1)); p.points[2] = Point::new_scale(facet.vertex[2](0), facet.vertex[2](1)); - p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that - pp.emplace_back(p); + p.make_counter_clockwise(); + paths.emplace_back(mittered_offset_path_scaled(p.points, deltas, 3.)); } // the offset factor was tuned using groovemount.stl - return union_ex(offset(pp, scale_(0.01)), true); + return ClipperPaths_to_Slic3rExPolygons(paths); } // 2D convex hull of a 3D mesh projected into the Z=0 plane. @@ -1797,7 +1799,7 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, const float clos // append to the supplied collection if (safety_offset > 0) - expolygons_append(*slices, offset2_ex(union_(loops, false), +safety_offset, -safety_offset)); + expolygons_append(*slices, offset2_ex(union_ex(loops, false), +safety_offset, -safety_offset)); else expolygons_append(*slices, union_ex(loops, false)); } From f0e1464ca3d5db5d7866cdca8d75476228b82905 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 13 Apr 2021 16:35:46 +0200 Subject: [PATCH 062/154] WIP: Fix of arrangement after reducing ClipperLib::cInt from int64_t to int32_t --- src/libnest2d/include/libnest2d/geometry_traits.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index d9c3d786266..3095c717dba 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -474,8 +474,8 @@ inline _Box

_Box

::infinite(const P& center) { // It is important for Mx and My to be strictly less than half of the // range of type C. width(), height() and area() will not overflow this way. - C Mx = C((std::numeric_limits::lowest() + 2 * getX(center)) / 2.01); - C My = C((std::numeric_limits::lowest() + 2 * getY(center)) / 2.01); + C Mx = C((std::numeric_limits::lowest() + 2 * getX(center)) / 4.01); + C My = C((std::numeric_limits::lowest() + 2 * getY(center)) / 4.01); ret.maxCorner() = center - P{Mx, My}; ret.minCorner() = center + P{Mx, My}; From d95f9425ce530d315cb8b9bb090bdac4addca42b Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Tue, 13 Apr 2021 19:09:54 +0200 Subject: [PATCH 063/154] creality.ini: Extrudr NX2 slightly lower temps After more practical testing, a slightly lower temp is beneficial on small pointy areas, preventing them from being slightly deformed. --- resources/profiles/Creality.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 0b4e4cf1403..ead535aed42 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -652,9 +652,9 @@ filament_colour = #FF0000 [filament:Extrudr PLA NX2 @CREALITY] inherits = *PLA* filament_vendor = Extrudr -temperature = 200 +temperature = 195 bed_temperature = 60 -first_layer_temperature = 205 +first_layer_temperature = 200 first_layer_bed_temperature = 60 filament_cost = 23.63 filament_density = 1.3 From e610102fa58345f7c82f9bee2f9b3b05a8985445 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 14 Apr 2021 07:04:25 +0200 Subject: [PATCH 064/154] Fixed build against wxWidgets 3.0 --- src/slic3r/GUI/MainFrame.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 5c390b66f67..9c426dcd965 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -522,8 +522,10 @@ void MainFrame::init_tabpanel() #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); #endif +#if wxCHECK_VERSION(3,1,3) if (wxSystemSettings::GetAppearance().IsDark()) m_tabpanel->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif m_tabpanel->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); From 847cdcb7cbf5f36f9e18425bf55f6bd06e339472 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 14 Apr 2021 08:51:54 +0200 Subject: [PATCH 065/154] Fix integer overflows in libnest2d tests --- tests/libnest2d/libnest2d_tests_main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index a0f1924603a..e3ffe9c6e33 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -1171,7 +1171,7 @@ TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") pconfig.object_function = [&pile_box](const Item &item) -> double { Box b = sl::boundingBox(item.boundingBox(), pile_box); - double area = b.area() / (W * W); + double area = b.area() / (double(W) * W); return -area; }; @@ -1187,5 +1187,5 @@ TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") // Here the result shall be a stairway of boxes REQUIRE(pile.size() == N); - REQUIRE(bb.area() == N * N * W * W); + REQUIRE(bb.area() == double(N) * N * W * W); } From 6007f7ee4965d2aefa9d204bd055a4c419a42a4e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 14 Apr 2021 09:22:51 +0200 Subject: [PATCH 066/154] Replacing ClipperLib::IntPoint with Eigen point as a first step to make the ClipperLib paths and polygons compatible with Slic3r paths and polygons without conversions and memory allocations. --- src/clipper/clipper.cpp | 584 +++++++++--------- src/clipper/clipper.hpp | 31 +- .../backends/clipper/clipper_polygon.hpp | 19 +- .../libnest2d/backends/clipper/geometries.hpp | 24 +- .../include/libnest2d/placers/nfpplacer.hpp | 19 +- src/libslic3r/Arrange.cpp | 6 +- src/libslic3r/Brim.cpp | 20 +- src/libslic3r/ClipperUtils.cpp | 8 +- src/libslic3r/Point.hpp | 66 +- src/libslic3r/SLA/AGGRaster.hpp | 4 +- src/libslic3r/SLAPrintSteps.cpp | 4 +- src/libslic3r/SVG.cpp | 4 +- tests/libnest2d/libnest2d_tests_main.cpp | 26 +- .../test_elephant_foot_compensation.cpp | 2 +- 14 files changed, 416 insertions(+), 401 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index cbe54a0647b..9f168100748 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -156,7 +156,7 @@ double Area(const Path &poly) double a = 0; for (int i = 0, j = size -1; i < size; ++i) { - a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + a += ((double)poly[j].x() + poly[i].x()) * ((double)poly[j].y() - poly[i].y()); j = i; } return -a * 0.5; @@ -169,7 +169,7 @@ double Area(const OutRec &outRec) if (!op) return 0; double a = 0; do { - a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); + a += (double)(op->Prev->Pt.x() + op->Pt.x()) * (double)(op->Prev->Pt.y() - op->Pt.y()); op = op->Next; } while (op != outRec.Pts); return a * 0.5; @@ -201,26 +201,26 @@ int PointInPolygon(const IntPoint &pt, const Path &path) for(size_t i = 1; i <= cnt; ++i) { IntPoint ipNext = (i == cnt ? path[0] : path[i]); - if (ipNext.Y == pt.Y && ((ipNext.X == pt.X) || (ip.Y == pt.Y && ((ipNext.X > pt.X) == (ip.X < pt.X))))) + if (ipNext.y() == pt.y() && ((ipNext.x() == pt.x()) || (ip.y() == pt.y() && ((ipNext.x() > pt.x()) == (ip.x() < pt.x()))))) return -1; - if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) + if ((ip.y() < pt.y()) != (ipNext.y() < pt.y())) { - if (ip.X >= pt.X) + if (ip.x() >= pt.x()) { - if (ipNext.X > pt.X) result = 1 - result; + if (ipNext.x() > pt.x()) result = 1 - result; else { - double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + double d = (double)(ip.x() - pt.x()) * (ipNext.y() - pt.y()) - (double)(ipNext.x() - pt.x()) * (ip.y() - pt.y()); if (!d) return -1; - if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result; } } else { - if (ipNext.X > pt.X) + if (ipNext.x() > pt.x()) { - double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + double d = (double)(ip.x() - pt.x()) * (ipNext.y() - pt.y()) - (double)(ipNext.x() - pt.x()) * (ip.y() - pt.y()); if (!d) return -1; - if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result; } } } @@ -238,29 +238,29 @@ int PointInPolygon (const IntPoint &pt, OutPt *op) OutPt* startOp = op; do { - if (op->Next->Pt.Y == pt.Y) + if (op->Next->Pt.y() == pt.y()) { - if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && - ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; + if ((op->Next->Pt.x() == pt.x()) || (op->Pt.y() == pt.y() && + ((op->Next->Pt.x() > pt.x()) == (op->Pt.x() < pt.x())))) return -1; } - if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) + if ((op->Pt.y() < pt.y()) != (op->Next->Pt.y() < pt.y())) { - if (op->Pt.X >= pt.X) + if (op->Pt.x() >= pt.x()) { - if (op->Next->Pt.X > pt.X) result = 1 - result; + if (op->Next->Pt.x() > pt.x()) result = 1 - result; else { - double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + double d = (double)(op->Pt.x() - pt.x()) * (op->Next->Pt.y() - pt.y()) - (double)(op->Next->Pt.x() - pt.x()) * (op->Pt.y() - pt.y()); if (!d) return -1; - if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + if ((d > 0) == (op->Next->Pt.y() > op->Pt.y())) result = 1 - result; } } else { - if (op->Next->Pt.X > pt.X) + if (op->Next->Pt.x() > pt.x()) { - double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + double d = (double)(op->Pt.x() - pt.x()) * (op->Next->Pt.y() - pt.y()) - (double)(op->Next->Pt.x() - pt.x()) * (op->Pt.y() - pt.y()); if (!d) return -1; - if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + if ((d > 0) == (op->Next->Pt.y() > op->Pt.y())) result = 1 - result; } } } @@ -304,100 +304,100 @@ inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cI #endif inline bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) - { return SlopesEqual(e1.Delta.X, e1.Delta.Y, e2.Delta.X, e2.Delta.Y, UseFullInt64Range); } + { return SlopesEqual(e1.Delta.x(), e1.Delta.y(), e2.Delta.x(), e2.Delta.y(), UseFullInt64Range); } inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3, bool UseFullInt64Range) - { return SlopesEqual(pt1.X-pt2.X, pt1.Y-pt2.Y, pt2.X-pt3.X, pt2.Y-pt3.Y, UseFullInt64Range); } + { return SlopesEqual(pt1.x()-pt2.x(), pt1.y()-pt2.y(), pt2.x()-pt3.x(), pt2.y()-pt3.y(), UseFullInt64Range); } inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3, const IntPoint &pt4, bool UseFullInt64Range) - { return SlopesEqual(pt1.X-pt2.X, pt1.Y-pt2.Y, pt3.X-pt4.X, pt3.Y-pt4.Y, UseFullInt64Range); } + { return SlopesEqual(pt1.x()-pt2.x(), pt1.y()-pt2.y(), pt3.x()-pt4.x(), pt3.y()-pt4.y(), UseFullInt64Range); } //------------------------------------------------------------------------------ inline bool IsHorizontal(TEdge &e) { - return e.Delta.Y == 0; + return e.Delta.y() == 0; } //------------------------------------------------------------------------------ inline double GetDx(const IntPoint &pt1, const IntPoint &pt2) { - return (pt1.Y == pt2.Y) ? - HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); + return (pt1.y() == pt2.y()) ? + HORIZONTAL : (double)(pt2.x() - pt1.x()) / (pt2.y() - pt1.y()); } //--------------------------------------------------------------------------- inline cInt TopX(TEdge &edge, const cInt currentY) { - return (currentY == edge.Top.Y) ? - edge.Top.X : - edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); + return (currentY == edge.Top.y()) ? + edge.Top.x() : + edge.Bot.x() + Round(edge.Dx *(currentY - edge.Bot.y())); } //------------------------------------------------------------------------------ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) { #ifdef use_xyz - ip.Z = 0; + ip.z() = 0; #endif double b1, b2; if (Edge1.Dx == Edge2.Dx) { - ip.Y = Edge1.Curr.Y; - ip.X = TopX(Edge1, ip.Y); + ip.y() = Edge1.Curr.y(); + ip.x() = TopX(Edge1, ip.y()); return; } - else if (Edge1.Delta.X == 0) + else if (Edge1.Delta.x() == 0) { - ip.X = Edge1.Bot.X; + ip.x() = Edge1.Bot.x(); if (IsHorizontal(Edge2)) - ip.Y = Edge2.Bot.Y; + ip.y() = Edge2.Bot.y(); else { - b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); - ip.Y = Round(ip.X / Edge2.Dx + b2); + b2 = Edge2.Bot.y() - (Edge2.Bot.x() / Edge2.Dx); + ip.y() = Round(ip.x() / Edge2.Dx + b2); } } - else if (Edge2.Delta.X == 0) + else if (Edge2.Delta.x() == 0) { - ip.X = Edge2.Bot.X; + ip.x() = Edge2.Bot.x(); if (IsHorizontal(Edge1)) - ip.Y = Edge1.Bot.Y; + ip.y() = Edge1.Bot.y(); else { - b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); - ip.Y = Round(ip.X / Edge1.Dx + b1); + b1 = Edge1.Bot.y() - (Edge1.Bot.x() / Edge1.Dx); + ip.y() = Round(ip.x() / Edge1.Dx + b1); } } else { - b1 = double(Edge1.Bot.X) - double(Edge1.Bot.Y) * Edge1.Dx; - b2 = double(Edge2.Bot.X) - double(Edge2.Bot.Y) * Edge2.Dx; + b1 = double(Edge1.Bot.x()) - double(Edge1.Bot.y()) * Edge1.Dx; + b2 = double(Edge2.Bot.x()) - double(Edge2.Bot.y()) * Edge2.Dx; double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); - ip.Y = Round(q); - ip.X = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ? + ip.y() = Round(q); + ip.x() = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ? Round(Edge1.Dx * q + b1) : Round(Edge2.Dx * q + b2); } - if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) + if (ip.y() < Edge1.Top.y() || ip.y() < Edge2.Top.y()) { - if (Edge1.Top.Y > Edge2.Top.Y) - ip.Y = Edge1.Top.Y; + if (Edge1.Top.y() > Edge2.Top.y()) + ip.y() = Edge1.Top.y(); else - ip.Y = Edge2.Top.Y; + ip.y() = Edge2.Top.y(); if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) - ip.X = TopX(Edge1, ip.Y); + ip.x() = TopX(Edge1, ip.y()); else - ip.X = TopX(Edge2, ip.Y); + ip.x() = TopX(Edge2, ip.y()); } - //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... - if (ip.Y > Edge1.Curr.Y) + //finally, don't allow 'ip' to be BELOW curr.y() (ie bottom of scanbeam) ... + if (ip.y() > Edge1.Curr.y()) { - ip.Y = Edge1.Curr.Y; + ip.y() = Edge1.Curr.y(); //use the more vertical edge to derive X ... if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) - ip.X = TopX(Edge2, ip.Y); else - ip.X = TopX(Edge1, ip.Y); + ip.x() = TopX(Edge2, ip.y()); else + ip.x() = TopX(Edge1, ip.y()); } } //------------------------------------------------------------------------------ @@ -429,7 +429,7 @@ inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) void InitEdge2(TEdge& e, PolyType Pt) { - if (e.Curr.Y >= e.Next->Curr.Y) + if (e.Curr.y() >= e.Next->Curr.y()) { e.Bot = e.Curr; e.Top = e.Next->Curr; @@ -439,11 +439,11 @@ void InitEdge2(TEdge& e, PolyType Pt) e.Bot = e.Next->Curr; } - e.Delta.X = (e.Top.X - e.Bot.X); - e.Delta.Y = (e.Top.Y - e.Bot.Y); + e.Delta.x() = (e.Top.x() - e.Bot.x()); + e.Delta.y() = (e.Top.y() - e.Bot.y()); - if (e.Delta.Y == 0) e.Dx = HORIZONTAL; - else e.Dx = (double)(e.Delta.X) / e.Delta.Y; + if (e.Delta.y() == 0) e.Dx = HORIZONTAL; + else e.Dx = (double)(e.Delta.x()) / e.Delta.y(); e.PolyTyp = Pt; } @@ -466,9 +466,9 @@ inline void ReverseHorizontal(TEdge &e) //swap horizontal edges' Top and Bottom x's so they follow the natural //progression of the bounds - ie so their xbots will align with the //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - std::swap(e.Top.X, e.Bot.X); + std::swap(e.Top.x(), e.Bot.x()); #ifdef use_xyz - std::swap(e.Top.Z, e.Bot.Z); + std::swap(e.Top.z(), e.Bot.z()); #endif } //------------------------------------------------------------------------------ @@ -477,20 +477,20 @@ bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) { //precondition: segments are Collinear. - if (std::abs(pt1a.X - pt1b.X) > std::abs(pt1a.Y - pt1b.Y)) + if (std::abs(pt1a.x() - pt1b.x()) > std::abs(pt1a.y() - pt1b.y())) { - if (pt1a.X > pt1b.X) std::swap(pt1a, pt1b); - if (pt2a.X > pt2b.X) std::swap(pt2a, pt2b); - if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; - return pt1.X < pt2.X; + if (pt1a.x() > pt1b.x()) std::swap(pt1a, pt1b); + if (pt2a.x() > pt2b.x()) std::swap(pt2a, pt2b); + if (pt1a.x() > pt2a.x()) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.x() < pt2b.x()) pt2 = pt1b; else pt2 = pt2b; + return pt1.x() < pt2.x(); } else { - if (pt1a.Y < pt1b.Y) std::swap(pt1a, pt1b); - if (pt2a.Y < pt2b.Y) std::swap(pt2a, pt2b); - if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; - return pt1.Y > pt2.Y; + if (pt1a.y() < pt1b.y()) std::swap(pt1a, pt1b); + if (pt2a.y() < pt2b.y()) std::swap(pt2a, pt2b); + if (pt1a.y() < pt2a.y()) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.y() > pt2b.y()) pt2 = pt1b; else pt2 = pt2b; + return pt1.y() > pt2.y(); } } //------------------------------------------------------------------------------ @@ -521,14 +521,14 @@ OutPt* GetBottomPt(OutPt *pp) OutPt* p = pp->Next; while (p != pp) { - if (p->Pt.Y > pp->Pt.Y) + if (p->Pt.y() > pp->Pt.y()) { pp = p; dups = 0; } - else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) + else if (p->Pt.y() == pp->Pt.y() && p->Pt.x() <= pp->Pt.x()) { - if (p->Pt.X < pp->Pt.X) + if (p->Pt.x() < pp->Pt.x()) { dups = 0; pp = p; @@ -558,10 +558,10 @@ bool Pt2IsBetweenPt1AndPt3(const IntPoint &pt1, { if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) return false; - else if (pt1.X != pt3.X) - return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else if (pt1.x() != pt3.x()) + return (pt2.x() > pt1.x()) == (pt2.x() < pt3.x()); else - return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); + return (pt2.y() > pt1.y()) == (pt2.y() < pt3.y()); } //------------------------------------------------------------------------------ @@ -582,10 +582,10 @@ inline void RangeTest(const IntPoint& Pt, bool& useFullRange) { if (useFullRange) { - if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + if (Pt.x() > hiRange || Pt.y() > hiRange || -Pt.x() > hiRange || -Pt.y() > hiRange) throw clipperException("Coordinate outside allowed range"); } - else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + else if (Pt.x() > loRange|| Pt.y() > loRange || -Pt.x() > loRange || -Pt.y() > loRange) { useFullRange = true; RangeTest(Pt, useFullRange); @@ -605,8 +605,8 @@ inline TEdge* FindNextLocMin(TEdge* E) while (IsHorizontal(*E->Prev)) E = E->Prev; TEdge* E2 = E; while (IsHorizontal(*E)) E = E->Next; - if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. - if (E2->Prev->Bot.X < E->Bot.X) E = E2; + if (E->Top.y() == E->Prev->Bot.y()) continue; //ie just an intermediate horz. + if (E2->Prev->Bot.x() < E->Bot.x()) E = E2; break; } return E; @@ -625,14 +625,14 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) //create another LocMin and call ProcessBound once more if (NextIsForward) { - while (E->Top.Y == E->Next->Bot.Y) E = E->Next; + while (E->Top.y() == E->Next->Bot.y()) E = E->Next; //don't include top horizontals when parsing a bound a second time, //they will be contained in the opposite bound ... while (E != Result && IsHorizontal(*E)) E = E->Prev; } else { - while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; + while (E->Top.y() == E->Prev->Bot.y()) E = E->Prev; while (E != Result && IsHorizontal(*E)) E = E->Next; } @@ -649,7 +649,7 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) else E = Result->Prev; LocalMinimum locMin; - locMin.Y = E->Bot.Y; + locMin.Y = E->Bot.y(); locMin.LeftBound = 0; locMin.RightBound = E; E->WindDelta = 0; @@ -672,17 +672,17 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) EStart = E->Next; if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge { - if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) + if (EStart->Bot.x() != E->Bot.x() && EStart->Top.x() != E->Bot.x()) ReverseHorizontal(*E); } - else if (EStart->Bot.X != E->Bot.X) + else if (EStart->Bot.x() != E->Bot.x()) ReverseHorizontal(*E); } EStart = E; if (NextIsForward) { - while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + while (Result->Top.y() == Result->Next->Bot.y() && Result->Next->OutIdx != Skip) Result = Result->Next; if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) { @@ -691,38 +691,38 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) //unless a Skip edge is encountered when that becomes the top divide Horz = Result; while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + if (Horz->Prev->Top.x() > Result->Next->Top.x()) Result = Horz->Prev; } while (E != Result) { E->NextInLML = E->Next; if (IsHorizontal(*E) && E != EStart && - E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E->Bot.x() != E->Prev->Top.x()) ReverseHorizontal(*E); E = E->Next; } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + if (IsHorizontal(*E) && E != EStart && E->Bot.x() != E->Prev->Top.x()) ReverseHorizontal(*E); Result = Result->Next; //move to the edge just beyond current bound } else { - while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + while (Result->Top.y() == Result->Prev->Bot.y() && Result->Prev->OutIdx != Skip) Result = Result->Prev; if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) { Horz = Result; while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X || - Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + if (Horz->Next->Top.x() == Result->Prev->Top.x() || + Horz->Next->Top.x() > Result->Prev->Top.x()) Result = Horz->Next; } while (E != Result) { E->NextInLML = E->Prev; - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + if (IsHorizontal(*E) && E != EStart && E->Bot.x() != E->Next->Top.x()) ReverseHorizontal(*E); E = E->Prev; } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + if (IsHorizontal(*E) && E != EStart && E->Bot.x() != E->Next->Top.x()) ReverseHorizontal(*E); Result = Result->Prev; //move to the edge just beyond current bound } @@ -887,7 +887,7 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b { InitEdge2(*E, PolyTyp); E = E->Next; - if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; + if (IsFlat && E->Curr.y() != eStart->Curr.y()) IsFlat = false; } while (E != eStart); @@ -903,14 +903,14 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b } E->Prev->OutIdx = Skip; LocalMinimum locMin; - locMin.Y = E->Bot.Y; + locMin.Y = E->Bot.y(); locMin.LeftBound = 0; locMin.RightBound = E; locMin.RightBound->Side = esRight; locMin.RightBound->WindDelta = 0; for (;;) { - if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + if (E->Bot.x() != E->Prev->Top.x()) ReverseHorizontal(*E); if (E->Next->OutIdx == Skip) break; E->NextInLML = E->Next; E = E->Next; @@ -937,7 +937,7 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b //E and E.Prev now share a local minima (left aligned if horizontal). //Compare their slopes to find which starts which bound ... LocalMinimum locMin; - locMin.Y = E->Bot.Y; + locMin.Y = E->Bot.y(); if (E->Dx < E->Prev->Dx) { locMin.LeftBound = E->Prev; @@ -1028,27 +1028,27 @@ IntRect ClipperBase::GetBounds() result.left = result.top = result.right = result.bottom = 0; return result; } - result.left = lm->LeftBound->Bot.X; - result.top = lm->LeftBound->Bot.Y; - result.right = lm->LeftBound->Bot.X; - result.bottom = lm->LeftBound->Bot.Y; + result.left = lm->LeftBound->Bot.x(); + result.top = lm->LeftBound->Bot.y(); + result.right = lm->LeftBound->Bot.x(); + result.bottom = lm->LeftBound->Bot.y(); while (lm != m_MinimaList.end()) { - result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); + result.bottom = std::max(result.bottom, lm->LeftBound->Bot.y()); TEdge* e = lm->LeftBound; for (;;) { TEdge* bottomE = e; while (e->NextInLML) { - if (e->Bot.X < result.left) result.left = e->Bot.X; - if (e->Bot.X > result.right) result.right = e->Bot.X; + if (e->Bot.x() < result.left) result.left = e->Bot.x(); + if (e->Bot.x() > result.right) result.right = e->Bot.x(); e = e->NextInLML; } - result.left = std::min(result.left, e->Bot.X); - result.right = std::max(result.right, e->Bot.X); - result.left = std::min(result.left, e->Top.X); - result.right = std::max(result.right, e->Top.X); - result.top = std::min(result.top, e->Top.Y); + result.left = std::min(result.left, e->Bot.x()); + result.right = std::max(result.right, e->Bot.x()); + result.left = std::min(result.left, e->Top.x()); + result.right = std::max(result.right, e->Top.x()); + result.top = std::min(result.top, e->Top.y()); if (bottomE == lm->LeftBound) e = lm->RightBound; else break; } @@ -1454,7 +1454,7 @@ OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) } if (prevE && prevE->OutIdx >= 0 && - (TopX(*prevE, Pt.Y) == TopX(*e, Pt.Y)) && + (TopX(*prevE, Pt.y()) == TopX(*e, Pt.y())) && SlopesEqual(*e, *prevE, m_UseFullRange) && (e->WindDelta != 0) && (prevE->WindDelta != 0)) { @@ -1540,7 +1540,7 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) SetWindingCount(*lb); if (IsContributing(*lb)) Op1 = AddOutPt(lb, lb->Bot); - m_Scanbeam.push(lb->Top.Y); + m_Scanbeam.push(lb->Top.y()); } else { @@ -1551,13 +1551,13 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) rb->WindCnt2 = lb->WindCnt2; if (IsContributing(*lb)) Op1 = AddLocalMinPoly(lb, rb, lb->Bot); - m_Scanbeam.push(lb->Top.Y); + m_Scanbeam.push(lb->Top.y()); } if (rb) { if(IsHorizontal(*rb)) AddEdgeToSEL(rb); - else m_Scanbeam.push(rb->Top.Y); + else m_Scanbeam.push(rb->Top.y()); } if (!lb || !rb) continue; @@ -1569,12 +1569,12 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) for (Join &jr : m_GhostJoins) //if the horizontal Rb and a 'ghost' horizontal overlap, then convert //the 'ghost' join to a real join ready for later ... - if (HorzSegmentsOverlap(jr.OutPt1->Pt.X, jr.OffPt.X, rb->Bot.X, rb->Top.X)) + if (HorzSegmentsOverlap(jr.OutPt1->Pt.x(), jr.OffPt.x(), rb->Bot.x(), rb->Top.x())) m_Joins.emplace_back(Join(jr.OutPt1, Op1, jr.OffPt)); } if (lb->OutIdx >= 0 && lb->PrevInAEL && - lb->PrevInAEL->Curr.X == lb->Bot.X && + lb->PrevInAEL->Curr.x() == lb->Bot.x() && lb->PrevInAEL->OutIdx >= 0 && SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) && (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) @@ -1640,11 +1640,11 @@ void Clipper::DeleteFromSEL(TEdge *e) #ifdef use_xyz void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) { - if (pt.Z != 0 || !m_ZFill) return; - else if (pt == e1.Bot) pt.Z = e1.Bot.Z; - else if (pt == e1.Top) pt.Z = e1.Top.Z; - else if (pt == e2.Bot) pt.Z = e2.Bot.Z; - else if (pt == e2.Top) pt.Z = e2.Top.Z; + if (pt.z() != 0 || !m_ZFill) return; + else if (pt == e1.Bot) pt.z() = e1.Bot.z(); + else if (pt == e1.Top) pt.z() = e1.Top.z(); + else if (pt == e2.Bot) pt.z() = e2.Bot.z(); + else if (pt == e2.Top) pt.z() = e2.Top.z(); else m_ZFill(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); } //------------------------------------------------------------------------------ @@ -1872,10 +1872,10 @@ OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) outRec2->BottomPt = GetBottomPt(outRec2->Pts); OutPt *OutPt1 = outRec1->BottomPt; OutPt *OutPt2 = outRec2->BottomPt; - if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; - else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; - else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; - else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; + if (OutPt1->Pt.y() > OutPt2->Pt.y()) return outRec1; + else if (OutPt1->Pt.y() < OutPt2->Pt.y()) return outRec2; + else if (OutPt1->Pt.x() < OutPt2->Pt.x()) return outRec1; + else if (OutPt1->Pt.x() > OutPt2->Pt.x()) return outRec2; else if (OutPt1->Next == OutPt1) return outRec2; else if (OutPt2->Next == OutPt2) return outRec1; else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; @@ -2081,13 +2081,13 @@ void Clipper::ProcessHorizontals() inline bool IsMaxima(TEdge *e, const cInt Y) { - return e && e->Top.Y == Y && !e->NextInLML; + return e && e->Top.y() == Y && !e->NextInLML; } //------------------------------------------------------------------------------ inline bool IsIntermediate(TEdge *e, const cInt Y) { - return e->Top.Y == Y && e->NextInLML; + return e->Top.y() == Y && e->NextInLML; } //------------------------------------------------------------------------------ @@ -2202,15 +2202,15 @@ void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) inline void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) { - if (HorzEdge.Bot.X < HorzEdge.Top.X) + if (HorzEdge.Bot.x() < HorzEdge.Top.x()) { - Left = HorzEdge.Bot.X; - Right = HorzEdge.Top.X; + Left = HorzEdge.Bot.x(); + Right = HorzEdge.Top.x(); Dir = dLeftToRight; } else { - Left = HorzEdge.Top.X; - Right = HorzEdge.Bot.X; + Left = HorzEdge.Top.x(); + Right = HorzEdge.Bot.x(); Dir = dRightToLeft; } } @@ -2219,8 +2219,8 @@ inline void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& /******************************************************************************* * Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * * Bottom of a scanbeam) are processed as if layered. The order in which HEs * -* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * -* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * +* are processed doesn't matter. HEs intersect with other HE Bot.x()s only [#] * +* (or they could intersect with Top.x()s only, ie EITHER Bot.x()s OR Top.x()s), * * and with other non-horizontal edges [*]. Once these intersections are * * processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * * the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * @@ -2248,15 +2248,15 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) if (dir == dLeftToRight) { maxIt = m_Maxima.begin(); - while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) ++maxIt; - if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) + while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.x()) ++maxIt; + if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.x()) maxIt = m_Maxima.end(); } else { maxRit = m_Maxima.rbegin(); - while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) ++maxRit; - if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) + while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.x()) ++maxRit; + if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.x()) maxRit = m_Maxima.rend(); } } @@ -2278,30 +2278,30 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) { if (dir == dLeftToRight) { - while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) + while (maxIt != m_Maxima.end() && *maxIt < e->Curr.x()) { if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); + AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.y())); ++maxIt; } } else { - while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) + while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.x()) { if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); + AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.y())); ++maxRit; } } }; - if ((dir == dLeftToRight && e->Curr.X > horzRight) || - (dir == dRightToLeft && e->Curr.X < horzLeft)) break; + if ((dir == dLeftToRight && e->Curr.x() > horzRight) || + (dir == dRightToLeft && e->Curr.x() < horzLeft)) break; //Also break if we've got to the end of an intermediate horizontal edge ... //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. - if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + if (e->Curr.x() == horzEdge->Top.x() && horzEdge->NextInLML && e->Dx < horzEdge->NextInLML->Dx) break; if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times @@ -2311,8 +2311,8 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) while (eNextHorz) { if (eNextHorz->OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge->Bot.X, - horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) + HorzSegmentsOverlap(horzEdge->Bot.x(), + horzEdge->Top.x(), eNextHorz->Bot.x(), eNextHorz->Top.x())) { OutPt* op2 = GetLastOutPt(eNextHorz); m_Joins.emplace_back(Join(op2, op1, eNextHorz->Top)); @@ -2335,12 +2335,12 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) if(dir == dLeftToRight) { - IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntPoint Pt = IntPoint(e->Curr.x(), horzEdge->Curr.y()); IntersectEdges(horzEdge, e, Pt); } else { - IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntPoint Pt = IntPoint(e->Curr.x(), horzEdge->Curr.y()); IntersectEdges( e, horzEdge, Pt); } TEdge* eNext = (dir == dLeftToRight) ? e->NextInAEL : e->PrevInAEL; @@ -2364,8 +2364,8 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) while (eNextHorz) { if (eNextHorz->OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge->Bot.X, - horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) + HorzSegmentsOverlap(horzEdge->Bot.x(), + horzEdge->Top.x(), eNextHorz->Bot.x(), eNextHorz->Top.x())) { OutPt* op2 = GetLastOutPt(eNextHorz); m_Joins.emplace_back(Join(op2, op1, eNextHorz->Top)); @@ -2385,17 +2385,17 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) //nb: HorzEdge is no longer horizontal here TEdge* ePrev = horzEdge->PrevInAEL; TEdge* eNext = horzEdge->NextInAEL; - if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && - ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && - (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + if (ePrev && ePrev->Curr.x() == horzEdge->Bot.x() && + ePrev->Curr.y() == horzEdge->Bot.y() && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.y() > ePrev->Top.y() && SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) { OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); m_Joins.emplace_back(Join(op1, op2, horzEdge->Top)); } - else if (eNext && eNext->Curr.X == horzEdge->Bot.X && - eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && - eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + else if (eNext && eNext->Curr.x() == horzEdge->Bot.x() && + eNext->Curr.y() == horzEdge->Bot.y() && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.y() > eNext->Top.y() && SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) { OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); @@ -2433,7 +2433,7 @@ void Clipper::UpdateEdgeIntoAEL(TEdge *&e) e->PrevInAEL = AelPrev; e->NextInAEL = AelNext; if (!IsHorizontal(*e)) - m_Scanbeam.push(e->Top.Y); + m_Scanbeam.push(e->Top.y()); } //------------------------------------------------------------------------------ @@ -2476,7 +2476,7 @@ void Clipper::BuildIntersectList(const cInt topY) { e->PrevInSEL = e->PrevInAEL; e->NextInSEL = e->NextInAEL; - e->Curr.X = TopX( *e, topY ); + e->Curr.x() = TopX( *e, topY ); e = e->NextInAEL; } @@ -2490,7 +2490,7 @@ void Clipper::BuildIntersectList(const cInt topY) { TEdge *eNext = e->NextInSEL; IntPoint Pt; - if(e->Curr.X > eNext->Curr.X) + if(e->Curr.x() > eNext->Curr.x()) { IntersectPoint(*e, *eNext, Pt); m_IntersectList.emplace_back(IntersectNode(e, eNext, Pt)); @@ -2522,7 +2522,7 @@ bool Clipper::FixupIntersectionOrder() //Now it's crucial that intersections are made only between adjacent edges, //so to ensure this the order of intersections may need adjusting ... CopyAELToSEL(); - std::sort(m_IntersectList.begin(), m_IntersectList.end(), [](const IntersectNode &node1, const IntersectNode &node2) { return node2.Pt.Y < node1.Pt.Y; }); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), [](const IntersectNode &node1, const IntersectNode &node2) { return node2.Pt.y() < node1.Pt.y(); }); size_t cnt = m_IntersectList.size(); for (size_t i = 0; i < cnt; ++i) @@ -2610,7 +2610,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) if(IsMaximaEdge) { - if (m_StrictSimple) m_Maxima.push_back(e->Top.X); + if (m_StrictSimple) m_Maxima.push_back(e->Top.x()); TEdge* ePrev = e->PrevInAEL; DoMaxima(e); if( !ePrev ) e = m_ActiveEdges; @@ -2618,7 +2618,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) } else { - //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + //2. promote horizontal edges, otherwise update Curr.x() and Curr.y() ... if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) { UpdateEdgeIntoAEL(e); @@ -2628,8 +2628,8 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) } else { - e->Curr.X = TopX( *e, topY ); - e->Curr.Y = topY; + e->Curr.x() = TopX( *e, topY ); + e->Curr.y() = topY; } //When StrictlySimple and 'e' is being touched by another edge, then @@ -2638,7 +2638,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) { TEdge* ePrev = e->PrevInAEL; if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && - (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) + (ePrev->Curr.x() == e->Curr.x()) && (ePrev->WindDelta != 0)) { IntPoint pt = e->Curr; #ifdef use_xyz @@ -2673,18 +2673,18 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) //if output polygons share an edge, they'll need joining later ... TEdge* ePrev = e->PrevInAEL; TEdge* eNext = e->NextInAEL; - if (ePrev && ePrev->Curr.X == e->Bot.X && - ePrev->Curr.Y == e->Bot.Y && op && - ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + if (ePrev && ePrev->Curr.x() == e->Bot.x() && + ePrev->Curr.y() == e->Bot.y() && op && + ePrev->OutIdx >= 0 && ePrev->Curr.y() > ePrev->Top.y() && SlopesEqual(*e, *ePrev, m_UseFullRange) && (e->WindDelta != 0) && (ePrev->WindDelta != 0)) { OutPt* op2 = AddOutPt(ePrev, e->Bot); m_Joins.emplace_back(Join(op, op2, e->Top)); } - else if (eNext && eNext->Curr.X == e->Bot.X && - eNext->Curr.Y == e->Bot.Y && op && - eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + else if (eNext && eNext->Curr.x() == e->Bot.x() && + eNext->Curr.y() == e->Bot.y() && op && + eNext->OutIdx >= 0 && eNext->Curr.y() > eNext->Top.y() && SlopesEqual(*e, *eNext, m_UseFullRange) && (e->WindDelta != 0) && (eNext->WindDelta != 0)) { @@ -2861,13 +2861,13 @@ void Clipper::BuildResult2(PolyTree& polytree) inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) { - if (e2.Curr.X == e1.Curr.X) + if (e2.Curr.x() == e1.Curr.x()) { - if (e2.Top.Y > e1.Top.Y) - return e2.Top.X < TopX(e1, e2.Top.Y); - else return e1.Top.X > TopX(e2, e1.Top.Y); + if (e2.Top.y() > e1.Top.y()) + return e2.Top.x() < TopX(e1, e2.Top.y()); + else return e1.Top.x() > TopX(e2, e1.Top.y()); } - else return e2.Curr.X < e1.Curr.X; + else return e2.Curr.x() < e1.Curr.x(); } //------------------------------------------------------------------------------ @@ -2956,8 +2956,8 @@ OutPt* Clipper::DupOutPt(OutPt* outPt, bool InsertAfter) bool Clipper::JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint &Pt, bool DiscardLeft) { - Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); - Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir1 = (op1->Pt.x() > op1b->Pt.x() ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.x() > op2b->Pt.x() ? dRightToLeft : dLeftToRight); if (Dir1 == Dir2) return false; //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we @@ -2967,10 +2967,10 @@ bool Clipper::JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) if (Dir1 == dLeftToRight) { - while (op1->Next->Pt.X <= Pt.X && - op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + while (op1->Next->Pt.x() <= Pt.x() && + op1->Next->Pt.x() >= op1->Pt.x() && op1->Next->Pt.y() == Pt.y()) op1 = op1->Next; - if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + if (DiscardLeft && (op1->Pt.x() != Pt.x())) op1 = op1->Next; op1b = this->DupOutPt(op1, !DiscardLeft); if (op1b->Pt != Pt) { @@ -2981,10 +2981,10 @@ bool Clipper::JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, } else { - while (op1->Next->Pt.X >= Pt.X && - op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + while (op1->Next->Pt.x() >= Pt.x() && + op1->Next->Pt.x() <= op1->Pt.x() && op1->Next->Pt.y() == Pt.y()) op1 = op1->Next; - if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.x() != Pt.x())) op1 = op1->Next; op1b = this->DupOutPt(op1, DiscardLeft); if (op1b->Pt != Pt) { @@ -2996,10 +2996,10 @@ bool Clipper::JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, if (Dir2 == dLeftToRight) { - while (op2->Next->Pt.X <= Pt.X && - op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + while (op2->Next->Pt.x() <= Pt.x() && + op2->Next->Pt.x() >= op2->Pt.x() && op2->Next->Pt.y() == Pt.y()) op2 = op2->Next; - if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + if (DiscardLeft && (op2->Pt.x() != Pt.x())) op2 = op2->Next; op2b = this->DupOutPt(op2, !DiscardLeft); if (op2b->Pt != Pt) { @@ -3009,10 +3009,10 @@ bool Clipper::JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, }; } else { - while (op2->Next->Pt.X >= Pt.X && - op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + while (op2->Next->Pt.x() >= Pt.x() && + op2->Next->Pt.x() <= op2->Pt.x() && op2->Next->Pt.y() == Pt.y()) op2 = op2->Next; - if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.x() != Pt.x())) op2 = op2->Next; op2b = this->DupOutPt(op2, DiscardLeft); if (op2b->Pt != Pt) { @@ -3052,7 +3052,7 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) //location at the Bottom of the overlapping segment (& Join.OffPt is above). //3. StrictSimple joins where edges touch but are not collinear and where //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. - bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + bool isHorizontal = (j->OutPt1->Pt.y() == j->OffPt.y()); if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && (j->OffPt == j->OutPt2->Pt)) @@ -3062,11 +3062,11 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) op1b = j->OutPt1->Next; while (op1b != op1 && (op1b->Pt == j->OffPt)) op1b = op1b->Next; - bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + bool reverse1 = (op1b->Pt.y() > j->OffPt.y()); op2b = j->OutPt2->Next; while (op2b != op2 && (op2b->Pt == j->OffPt)) op2b = op2b->Next; - bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + bool reverse2 = (op2b->Pt.y() > j->OffPt.y()); if (reverse1 == reverse2) return false; if (reverse1) { @@ -3098,22 +3098,22 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt //may be anywhere along the horizontal edge. op1b = op1; - while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) + while (op1->Prev->Pt.y() == op1->Pt.y() && op1->Prev != op1b && op1->Prev != op2) op1 = op1->Prev; - while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) + while (op1b->Next->Pt.y() == op1b->Pt.y() && op1b->Next != op1 && op1b->Next != op2) op1b = op1b->Next; if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' op2b = op2; - while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) + while (op2->Prev->Pt.y() == op2->Pt.y() && op2->Prev != op2b && op2->Prev != op1b) op2 = op2->Prev; - while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) + while (op2b->Next->Pt.y() == op2b->Pt.y() && op2b->Next != op2 && op2b->Next != op1) op2b = op2b->Next; if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' cInt Left, Right; //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges - if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + if (!GetOverlap(op1->Pt.x(), op1b->Pt.x(), op2->Pt.x(), op2b->Pt.x(), Left, Right)) return false; //DiscardLeftSide: when overlapping edges are joined, a spike will created @@ -3121,51 +3121,51 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) //on the discard Side as either may still be needed for other joins ... IntPoint Pt; bool DiscardLeftSide; - if (op1->Pt.X >= Left && op1->Pt.X <= Right) + if (op1->Pt.x() >= Left && op1->Pt.x() <= Right) { - Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + Pt = op1->Pt; DiscardLeftSide = (op1->Pt.x() > op1b->Pt.x()); } - else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) + else if (op2->Pt.x() >= Left&& op2->Pt.x() <= Right) { - Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + Pt = op2->Pt; DiscardLeftSide = (op2->Pt.x() > op2b->Pt.x()); } - else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) + else if (op1b->Pt.x() >= Left && op1b->Pt.x() <= Right) { - Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.x() > op1->Pt.x(); } else { - Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.x() > op2->Pt.x()); } j->OutPt1 = op1; j->OutPt2 = op2; return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); } else { //nb: For non-horizontal joins ... - // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y - // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + // 1. Jr.OutPt1.Pt.y() == Jr.OutPt2.Pt.y() + // 2. Jr.OutPt1.Pt > Jr.OffPt.y() //make sure the polygons are correctly oriented ... op1b = op1->Next; while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; - bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + bool Reverse1 = ((op1b->Pt.y() > op1->Pt.y()) || !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); if (Reverse1) { op1b = op1->Prev; while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; - if ((op1b->Pt.Y > op1->Pt.Y) || + if ((op1b->Pt.y() > op1->Pt.y()) || !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; }; op2b = op2->Next; while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; - bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + bool Reverse2 = ((op2b->Pt.y() > op2->Pt.y()) || !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); if (Reverse2) { op2b = op2->Prev; while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; - if ((op2b->Pt.Y > op2->Pt.Y) || + if ((op2b->Pt.y() > op2->Pt.y()) || !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; } @@ -3334,11 +3334,11 @@ void Clipper::JoinCommonEdges() DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) { - if(pt2.X == pt1.X && pt2.Y == pt1.Y) + if(pt2.x() == pt1.x() && pt2.y() == pt1.y()) return DoublePoint(0, 0); - double Dx = double(pt2.X - pt1.X); - double dy = double(pt2.Y - pt1.Y); + double Dx = double(pt2.x() - pt1.x()); + double dy = double(pt2.y() - pt1.y()); double f = 1.0 / std::sqrt( Dx*Dx + dy*dy ); Dx *= f; dy *= f; @@ -3354,7 +3354,7 @@ void ClipperOffset::Clear() for (int i = 0; i < m_polyNodes.ChildCount(); ++i) delete m_polyNodes.Childs[i]; m_polyNodes.Childs.clear(); - m_lowest.X = -1; + m_lowest.x() = -1; } //------------------------------------------------------------------------------ @@ -3373,8 +3373,8 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType for (; highI > 0; -- highI) { bool same = false; if (has_shortest_edge_length) { - double dx = double(path[highI].X - path[0].X); - double dy = double(path[highI].Y - path[0].Y); + double dx = double(path[highI].x() - path[0].x()); + double dy = double(path[highI].y() - path[0].y()); same = dx*dx + dy*dy < shortest_edge_length2; } else same = path[0] == path[highI]; @@ -3387,8 +3387,8 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType for (int i = 1; i <= highI; i++) { bool same = false; if (has_shortest_edge_length) { - double dx = double(path[i].X - newNode->Contour[j].X); - double dy = double(path[i].Y - newNode->Contour[j].Y); + double dx = double(path[i].x() - newNode->Contour[j].x()); + double dy = double(path[i].y() - newNode->Contour[j].y()); same = dx*dx + dy*dy < shortest_edge_length2; } else same = newNode->Contour[j] == path[i]; @@ -3396,9 +3396,9 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType continue; j++; newNode->Contour.push_back(path[i]); - if (path[i].Y > newNode->Contour[k].Y || - (path[i].Y == newNode->Contour[k].Y && - path[i].X < newNode->Contour[k].X)) k = j; + if (path[i].y() > newNode->Contour[k].y() || + (path[i].y() == newNode->Contour[k].y() && + path[i].x() < newNode->Contour[k].x())) k = j; } if (endType == etClosedPolygon && j < 2) { @@ -3409,14 +3409,14 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType //if this path's lowest pt is lower than all the others then update m_lowest if (endType != etClosedPolygon) return; - if (m_lowest.X < 0) + if (m_lowest.x() < 0) m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); else { - IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; - if (newNode->Contour[k].Y > ip.Y || - (newNode->Contour[k].Y == ip.Y && - newNode->Contour[k].X < ip.X)) + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.x()]->Contour[(int)m_lowest.y()]; + if (newNode->Contour[k].y() > ip.y() || + (newNode->Contour[k].y() == ip.y() && + newNode->Contour[k].x() < ip.x())) m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); } } @@ -3433,8 +3433,8 @@ void ClipperOffset::FixOrientations() { //fixup orientations of all closed paths if the orientation of the //closed path with the lowermost vertex is wrong ... - if (m_lowest.X >= 0 && - !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) + if (m_lowest.x() >= 0 && + !Orientation(m_polyNodes.Childs[(int)m_lowest.x()]->Contour)) { for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { @@ -3582,8 +3582,8 @@ void ClipperOffset::DoOffset(double delta) for (cInt j = 1; j <= steps; j++) { m_destPoly.push_back(IntPoint( - Round(m_srcPoly[0].X + X * delta), - Round(m_srcPoly[0].Y + Y * delta))); + Round(m_srcPoly[0].x() + X * delta), + Round(m_srcPoly[0].y() + Y * delta))); double X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; @@ -3595,8 +3595,8 @@ void ClipperOffset::DoOffset(double delta) for (int j = 0; j < 4; ++j) { m_destPoly.push_back(IntPoint( - Round(m_srcPoly[0].X + X * delta), - Round(m_srcPoly[0].Y + Y * delta))); + Round(m_srcPoly[0].x() + X * delta), + Round(m_srcPoly[0].y() + Y * delta))); if (X < 0) X = 1; else if (Y < 0) Y = 1; else X = -1; @@ -3632,8 +3632,8 @@ void ClipperOffset::DoOffset(double delta) //re-build m_normals ... DoublePoint n = m_normals[len -1]; for (int j = len - 1; j > 0; j--) - m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); - m_normals[0] = DoublePoint(-n.X, -n.Y); + m_normals[j] = DoublePoint(-m_normals[j - 1].x(), -m_normals[j - 1].y()); + m_normals[0] = DoublePoint(-n.x(), -n.y()); k = 0; for (int j = len - 1; j >= 0; j--) OffsetPoint(j, k, node.m_jointype); @@ -3649,9 +3649,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { int j = len - 1; - pt1 = IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * delta), Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[j].X - m_normals[j].X * delta), Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta)); m_destPoly.push_back(pt1); } else @@ -3659,7 +3659,7 @@ void ClipperOffset::DoOffset(double delta) int j = len - 1; k = len - 2; m_sinA = 0; - m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + m_normals[j] = DoublePoint(-m_normals[j].x(), -m_normals[j].y()); if (node.m_endtype == etOpenSquare) DoSquare(j, k); else @@ -3668,17 +3668,17 @@ void ClipperOffset::DoOffset(double delta) //re-build m_normals ... for (int j = len - 1; j > 0; j--) - m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); - m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + m_normals[j] = DoublePoint(-m_normals[j - 1].x(), -m_normals[j - 1].y()); + m_normals[0] = DoublePoint(-m_normals[1].x(), -m_normals[1].y()); k = len - 1; for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); if (node.m_endtype == etOpenButt) { - pt1 = IntPoint(Round(m_srcPoly[0].X - m_normals[0].X * delta), Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[0].X + m_normals[0].X * delta), Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + pt1 = IntPoint(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta)); m_destPoly.push_back(pt1); } else @@ -3699,15 +3699,15 @@ void ClipperOffset::DoOffset(double delta) void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) { //cross product ... - m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + m_sinA = (m_normals[k].x() * m_normals[j].y() - m_normals[j].x() * m_normals[k].y()); if (std::fabs(m_sinA * m_delta) < 1.0) { //dot product ... - double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); + double cosA = (m_normals[k].x() * m_normals[j].x() + m_normals[j].y() * m_normals[k].y() ); if (cosA > 0) // angle => 0 degrees { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), + Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); return; } //else angle => 180 degrees @@ -3717,19 +3717,19 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) if (m_sinA * m_delta < 0) { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), + Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); m_destPoly.push_back(m_srcPoly[j]); - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), + Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); } else switch (jointype) { case jtMiter: { - double r = 1 + (m_normals[j].X * m_normals[k].X + - m_normals[j].Y * m_normals[k].Y); + double r = 1 + (m_normals[j].x() * m_normals[k].x() + + m_normals[j].y() * m_normals[k].y()); if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); break; } @@ -3743,43 +3743,43 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) void ClipperOffset::DoSquare(int j, int k) { double dx = std::tan(std::atan2(m_sinA, - m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()) / 4); m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), - Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + Round(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)), + Round(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx)))); m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), - Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); + Round(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)), + Round(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx)))); } //------------------------------------------------------------------------------ void ClipperOffset::DoMiter(int j, int k, double r) { double q = m_delta / r; - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), - Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q), + Round(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q))); } //------------------------------------------------------------------------------ void ClipperOffset::DoRound(int j, int k) { double a = std::atan2(m_sinA, - m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()); auto steps = std::max(Round(m_StepsPerRad * std::fabs(a)), 1); - double X = m_normals[k].X, Y = m_normals[k].Y, X2; + double X = m_normals[k].x(), Y = m_normals[k].y(), X2; for (int i = 0; i < steps; ++i) { m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + X * m_delta), - Round(m_srcPoly[j].Y + Y * m_delta))); + Round(m_srcPoly[j].x() + X * m_delta), + Round(m_srcPoly[j].y() + Y * m_delta))); X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_normals[j].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), + Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); } //------------------------------------------------------------------------------ @@ -3897,8 +3897,8 @@ void SimplifyPolygons(Paths &polys, PolyFillType fillType) inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) { - auto Dx = double(pt1.X - pt2.X); - auto dy = double(pt1.Y - pt2.Y); + auto Dx = double(pt1.x() - pt2.x()); + auto dy = double(pt1.y() - pt2.y()); return (Dx*Dx + dy*dy); } //------------------------------------------------------------------------------ @@ -3912,10 +3912,10 @@ double DistanceFromLineSqrd( //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) //see http://en.wikipedia.org/wiki/Perpendicular_distance - double A = double(ln1.Y - ln2.Y); - double B = double(ln2.X - ln1.X); - double C = A * ln1.X + B * ln1.Y; - C = A * pt.X + B * pt.Y - C; + double A = double(ln1.y() - ln2.y()); + double B = double(ln2.x() - ln1.x()); + double C = A * ln1.x() + B * ln1.y(); + C = A * pt.x() + B * pt.y() - C; return (C * C) / (A * A + B * B); } //--------------------------------------------------------------------------- @@ -3926,20 +3926,20 @@ bool SlopesNearCollinear(const IntPoint& pt1, //this function is more accurate when the point that's geometrically //between the other 2 points is the one that's tested for distance. //ie makes it more likely to pick up 'spikes' ... - if (std::abs(pt1.X - pt2.X) > std::abs(pt1.Y - pt2.Y)) + if (std::abs(pt1.x() - pt2.x()) > std::abs(pt1.y() - pt2.y())) { - if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) + if ((pt1.x() > pt2.x()) == (pt1.x() < pt3.x())) return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) + else if ((pt2.x() > pt1.x()) == (pt2.x() < pt3.x())) return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; else return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; } else { - if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) + if ((pt1.y() > pt2.y()) == (pt1.y() < pt3.y())) return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) + else if ((pt2.y() > pt1.y()) == (pt2.y() < pt3.y())) return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; else return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; @@ -3949,8 +3949,8 @@ bool SlopesNearCollinear(const IntPoint& pt1, bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) { - auto Dx = double(pt1.X - pt2.X); - auto dy = double(pt1.Y - pt2.Y); + auto Dx = double(pt1.x() - pt2.x()); + auto dy = double(pt1.y() - pt2.y()); return ((Dx * Dx) + (dy * dy) <= distSqrd); } //------------------------------------------------------------------------------ @@ -4058,7 +4058,7 @@ void Minkowski(const Path& poly, const Path& path, Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + p.push_back(IntPoint(path[i].x() + poly[j].x(), path[i].y() + poly[j].y())); pp.push_back(p); } else @@ -4067,7 +4067,7 @@ void Minkowski(const Path& poly, const Path& path, Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + p.push_back(IntPoint(path[i].x() - poly[j].x(), path[i].y() - poly[j].y())); pp.push_back(p); } @@ -4102,7 +4102,7 @@ void TranslatePath(const Path& input, Path& output, const IntPoint& delta) //precondition: input != output output.resize(input.size()); for (size_t i = 0; i < input.size(); ++i) - output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); + output[i] = IntPoint(input[i].x() + delta.x(), input[i].y() + delta.y()); } //------------------------------------------------------------------------------ @@ -4178,7 +4178,7 @@ void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) std::ostream& operator <<(std::ostream &s, const IntPoint &p) { - s << "(" << p.X << "," << p.Y << ")"; + s << "(" << p.x() << "," << p.y() << ")"; return s; } //------------------------------------------------------------------------------ @@ -4188,8 +4188,8 @@ std::ostream& operator <<(std::ostream &s, const Path &p) if (p.empty()) return s; Path::size_type last = p.size() -1; for (Path::size_type i = 0; i < last; i++) - s << "(" << p[i].X << "," << p[i].Y << "), "; - s << "(" << p[last].X << "," << p[last].Y << ")\n"; + s << "(" << p[i].x() << "," << p[i].y() << "), "; + s << "(" << p[last].x() << "," << p[last].y() << ")\n"; return s; } //------------------------------------------------------------------------------ diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 48e83d04610..31919ce752c 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -37,6 +37,8 @@ #include #include +#include + #define CLIPPER_VERSION "6.2.6" //use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. @@ -88,6 +90,16 @@ enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; static constexpr cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; #endif // CLIPPERLIB_INT32 +#if 1 +using IntPoint = Eigen::Matrix; +using DoublePoint = Eigen::Matrix; +#else struct IntPoint { cInt X; cInt Y; @@ -107,10 +119,18 @@ struct IntPoint { return a.X != b.X || a.Y != b.Y; } }; +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + DoublePoint(IntPoint ip) : X((double)ip.x()), Y((double)ip.y()) {} +}; +#endif //------------------------------------------------------------------------------ -typedef std::vector< IntPoint > Path; -typedef std::vector< Path > Paths; +typedef std::vector Path; +typedef std::vector Paths; inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} @@ -119,13 +139,6 @@ std::ostream& operator <<(std::ostream &s, const IntPoint &p); std::ostream& operator <<(std::ostream &s, const Path &p); std::ostream& operator <<(std::ostream &s, const Paths &p); -struct DoublePoint -{ - double X; - double Y; - DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} - DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} -}; //------------------------------------------------------------------------------ #ifdef use_xyz diff --git a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp index 6511fbb72a4..d4fcd7af335 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp @@ -23,10 +23,12 @@ struct Polygon { Contour(std::move(cont)), Holes(std::move(holes)) {} }; +#if 0 inline IntPoint& operator +=(IntPoint& p, const IntPoint& pa ) { // This could be done with SIMD - p.X += pa.X; - p.Y += pa.Y; + + p.x() += pa.x(); + p.y() += pa.y(); return p; } @@ -37,15 +39,15 @@ inline IntPoint operator+(const IntPoint& p1, const IntPoint& p2) { } inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) { - p.X -= pa.X; - p.Y -= pa.Y; + p.x() -= pa.x(); + p.y() -= pa.y(); return p; } inline IntPoint operator -(const IntPoint& p ) { IntPoint ret = p; - ret.X = -ret.X; - ret.Y = -ret.Y; + ret.x() = -ret.x(); + ret.y() = -ret.y(); return ret; } @@ -56,8 +58,8 @@ inline IntPoint operator-(const IntPoint& p1, const IntPoint& p2) { } inline IntPoint& operator *=(IntPoint& p, const IntPoint& pa ) { - p.X *= pa.X; - p.Y *= pa.Y; + p.x() *= pa.x(); + p.y() *= pa.y(); return p; } @@ -66,6 +68,7 @@ inline IntPoint operator*(const IntPoint& p1, const IntPoint& p2) { ret *= p2; return ret; } +#endif } diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 9586db35c6a..5999ebf2a06 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -46,25 +46,25 @@ namespace pointlike { // Tell libnest2d how to extract the X coord from a ClipperPoint object template<> inline ClipperLib::cInt x(const PointImpl& p) { - return p.X; + return p.x(); } // Tell libnest2d how to extract the Y coord from a ClipperPoint object template<> inline ClipperLib::cInt y(const PointImpl& p) { - return p.Y; + return p.y(); } // Tell libnest2d how to extract the X coord from a ClipperPoint object template<> inline ClipperLib::cInt& x(PointImpl& p) { - return p.X; + return p.x(); } // Tell libnest2d how to extract the Y coord from a ClipperPoint object template<> inline ClipperLib::cInt& y(PointImpl& p) { - return p.Y; + return p.y(); } } @@ -144,7 +144,7 @@ template<> inline std::string toString(const PolygonImpl& sh) ss << "Contour {\n"; for(auto p : sh.Contour) { - ss << "\t" << p.X << " " << p.Y << "\n"; + ss << "\t" << p.x() << " " << p.y() << "\n"; } ss << "}\n"; @@ -152,7 +152,7 @@ template<> inline std::string toString(const PolygonImpl& sh) ss << "Holes {\n"; for(auto p : h) { ss << "\t{\n"; - ss << "\t\t" << p.X << " " << p.Y << "\n"; + ss << "\t\t" << p.x() << " " << p.y() << "\n"; ss << "\t}\n"; } ss << "}\n"; @@ -238,14 +238,14 @@ inline void rotate(PolygonImpl& sh, const Radians& rads) for(auto& p : sh.Contour) { p = { - static_cast(p.X * cosa - p.Y * sina), - static_cast(p.X * sina + p.Y * cosa) + static_cast(p.x() * cosa - p.y() * sina), + static_cast(p.x() * sina + p.y() * cosa) }; } for(auto& hole : sh.Holes) for(auto& p : hole) { p = { - static_cast(p.X * cosa - p.Y * sina), - static_cast(p.X * sina + p.Y * cosa) + static_cast(p.x() * cosa - p.y() * sina), + static_cast(p.x() * sina + p.y() * cosa) }; } } @@ -277,7 +277,7 @@ inline TMultiShape clipper_execute( if(!poly.Contour.empty() ) { auto front_p = poly.Contour.front(); auto &back_p = poly.Contour.back(); - if(front_p.X != back_p.X || front_p.Y != back_p.X) + if(front_p.x() != back_p.x() || front_p.y() != back_p.x()) poly.Contour.emplace_back(front_p); } @@ -294,7 +294,7 @@ inline TMultiShape clipper_execute( if(!poly.Contour.empty() ) { auto front_p = poly.Contour.front(); auto &back_p = poly.Contour.back(); - if(front_p.X != back_p.X || front_p.Y != back_p.X) + if(front_p.x() != back_p.x() || front_p.y() != back_p.x()) poly.Contour.emplace_back(front_p); } diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index bd9c6035589..70168c85ab2 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -250,8 +250,8 @@ template class EdgeCache { Vertex ret = edge.first(); // Get the point on the edge which lies in ed distance from the start - ret += { static_cast(std::round(ed*std::cos(angle))), - static_cast(std::round(ed*std::sin(angle))) }; + ret += Vertex(static_cast(std::round(ed*std::cos(angle))), + static_cast(std::round(ed*std::sin(angle)))); return ret; } @@ -344,7 +344,8 @@ inline void correctNfpPosition(nfp::NfpResult& nfp, auto dtouch = touch_sh - touch_other; auto top_other = orbiter.rightmostTopVertex() + dtouch; auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point - shapelike::translate(nfp.first, dnfp); + //FIXME the explicit type conversion ClipperLib::IntPoint() + shapelike::translate(nfp.first, ClipperLib::IntPoint(dnfp)); } template @@ -473,7 +474,8 @@ public: auto bbin = sl::boundingBox(bin); auto d = bbch.center() - bbin.center(); auto chullcpy = chull; - sl::translate(chullcpy, d); + //FIXME the explicit type conversion ClipperLib::IntPoint() + sl::translate(chullcpy, ClipperLib::IntPoint(d)); return sl::isInside(chullcpy, bin) ? -1.0 : 1.0; } @@ -724,8 +726,7 @@ private: auto rawobjfunc = [_objfunc, iv, startpos] (Vertex v, Item& itm) { - auto d = v - iv; - d += startpos; + auto d = (v - iv) + startpos; itm.translation(d); return _objfunc(itm); }; @@ -742,8 +743,7 @@ private: &item, &bin, &iv, &startpos] (const Optimum& o) { auto v = getNfpPoint(o); - auto d = v - iv; - d += startpos; + auto d = (v - iv) + startpos; item.translation(d); merged_pile.emplace_back(item.transformedShape()); @@ -877,8 +877,7 @@ private: } if( best_score < global_score ) { - auto d = getNfpPoint(optimum) - iv; - d += startpos; + auto d = (getNfpPoint(optimum) - iv) + startpos; final_tr = d; final_rot = initial_rot + rot; can_pack = true; diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 3800d49e318..91f35f8456c 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -56,8 +56,8 @@ template, int...EigenArgs> inline constexpr Eigen::Matrix unscaled( const ClipperLib::IntPoint &v) noexcept { - return Eigen::Matrix{unscaled(v.X), - unscaled(v.Y)}; + return Eigen::Matrix{unscaled(v.x()), + unscaled(v.y())}; } namespace arrangement { @@ -644,7 +644,7 @@ void arrange(ArrangePolygons & arrangables, for(size_t i = 0; i < items.size(); ++i) { clppr::IntPoint tr = items[i].translation(); - arrangables[i].translation = {coord_t(tr.X), coord_t(tr.Y)}; + arrangables[i].translation = {coord_t(tr.x()), coord_t(tr.y())}; arrangables[i].rotation = items[i].rotation(); arrangables[i].bed_idx = items[i].binId(); } diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 08bedc5c04c..19f5ae82e45 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -78,7 +78,7 @@ static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print) // Assign the maximum Z from four points. This values is valid index of the island clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { - pt.Z = std::max(std::max(e1bot.Z, e1top.Z), std::max(e2bot.Z, e2top.Z)); + pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z())); }); // Add islands clipper.AddPaths(islands_clip, ClipperLib_Z::ptSubject, true); @@ -90,9 +90,9 @@ static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print) ConstPrintObjectPtrs top_level_objects_with_brim; for (int i = 0; i < islands_polytree.ChildCount(); ++i) { for (const ClipperLib_Z::IntPoint &point : islands_polytree.Childs[i]->Contour) { - if (point.Z != 0 && processed_objects_idx.find(island_to_object[point.Z - 1]->id().id) == processed_objects_idx.end()) { - top_level_objects_with_brim.emplace_back(island_to_object[point.Z - 1]); - processed_objects_idx.insert(island_to_object[point.Z - 1]->id().id); + if (point.z() != 0 && processed_objects_idx.find(island_to_object[point.z() - 1]->id().id) == processed_objects_idx.end()) { + top_level_objects_with_brim.emplace_back(island_to_object[point.z() - 1]); + processed_objects_idx.insert(island_to_object[point.z() - 1]->id().id); } } } @@ -456,7 +456,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance clipper.ZFillFunction([](const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, ClipperLib_Z::IntPoint& pt) { // Assign a valid input loop identifier. Such an identifier is strictly positive, the next line is safe even in case one side of a segment // hat the Z coordinate not set to the contour coordinate. - pt.Z = std::max(std::max(e1bot.Z, e1top.Z), std::max(e2bot.Z, e2top.Z)); + pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z())); }); // add polygons clipper.AddPaths(input_clip, ClipperLib_Z::ptSubject, false); @@ -474,8 +474,8 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance for (const ClipperLib_Z::Path &path : loops_trimmed) { size_t input_idx = 0; for (const ClipperLib_Z::IntPoint &pt : path) - if (pt.Z > 0) { - input_idx = (size_t)pt.Z; + if (pt.z() > 0) { + input_idx = (size_t)pt.z(); break; } assert(input_idx != 0); @@ -492,14 +492,14 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance size_t j = i + 1; for (; j < loops_trimmed_order.size() && loops_trimmed_order[i].second == loops_trimmed_order[j].second; ++ j) ; const ClipperLib_Z::Path &first_path = *loops_trimmed_order[i].first; - if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) { + if (i + 1 == j && first_path.size() > 3 && first_path.front().x() == first_path.back().x() && first_path.front().y() == first_path.back().y()) { auto *loop = new ExtrusionLoop(); brim.entities.emplace_back(loop); loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); Points &points = loop->paths.front().polyline.points; points.reserve(first_path.size()); for (const ClipperLib_Z::IntPoint &pt : first_path) - points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); + points.emplace_back(coord_t(pt.x()), coord_t(pt.y())); i = j; } else { //FIXME The path chaining here may not be optimal. @@ -511,7 +511,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance Points &points = static_cast(this_loop_trimmed.entities.back())->polyline.points; points.reserve(path.size()); for (const ClipperLib_Z::IntPoint &pt : path) - points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); + points.emplace_back(coord_t(pt.x()), coord_t(pt.y())); } chain_and_reorder_extrusion_entities(this_loop_trimmed.entities, &last_pt); brim.entities.reserve(brim.entities.size() + this_loop_trimmed.entities.size()); diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index cd243dfb1ba..477dbf6f180 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -131,7 +131,7 @@ Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input) { Polygon retval; for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) - retval.points.emplace_back(pit->X, pit->Y); + retval.points.emplace_back(pit->x(), pit->y()); return retval; } @@ -139,7 +139,7 @@ Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input) { Polyline retval; for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) - retval.points.emplace_back(pit->X, pit->Y); + retval.points.emplace_back(pit->x(), pit->y()); return retval; } @@ -752,7 +752,7 @@ ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes) for (const ClipperLib::PolyNode *node : nodes) ordering_points.emplace_back( - Point(node->Contour.front().X, node->Contour.front().Y)); + Point(node->Contour.front().x(), node->Contour.front().y())); // perform the ordering ClipperLib::PolyNodes ordered_nodes = @@ -777,7 +777,7 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons Points ordering_points; ordering_points.reserve(nodes.size()); for (const ClipperLib::PolyNode *node : nodes) - ordering_points.emplace_back(node->Contour.front().X, node->Contour.front().Y); + ordering_points.emplace_back(node->Contour.front().x(), node->Contour.front().y()); // Perform the ordering, push results recursively. //FIXME pass the last point to chain_clipper_polynodes? diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index f38a7066b10..12870b71329 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -17,42 +17,42 @@ class BoundingBox; class Line; class MultiPoint; class Point; -typedef Point Vector; +using Vector = Point; // Eigen types, to replace the Slic3r's own types in the future. // Vector types with a fixed point coordinate base type. -typedef Eigen::Matrix Vec2crd; -typedef Eigen::Matrix Vec3crd; -typedef Eigen::Matrix Vec2i; -typedef Eigen::Matrix Vec3i; -typedef Eigen::Matrix Vec2i32; -typedef Eigen::Matrix Vec2i64; -typedef Eigen::Matrix Vec3i32; -typedef Eigen::Matrix Vec3i64; +using Vec2crd = Eigen::Matrix; +using Vec3crd = Eigen::Matrix; +using Vec2i = Eigen::Matrix; +using Vec3i = Eigen::Matrix; +using Vec2i32 = Eigen::Matrix; +using Vec2i64 = Eigen::Matrix; +using Vec3i32 = Eigen::Matrix; +using Vec3i64 = Eigen::Matrix; // Vector types with a double coordinate base type. -typedef Eigen::Matrix Vec2f; -typedef Eigen::Matrix Vec3f; -typedef Eigen::Matrix Vec2d; -typedef Eigen::Matrix Vec3d; +using Vec2f = Eigen::Matrix; +using Vec3f = Eigen::Matrix; +using Vec2d = Eigen::Matrix; +using Vec3d = Eigen::Matrix; -typedef std::vector Points; -typedef std::vector PointPtrs; -typedef std::vector PointConstPtrs; -typedef std::vector Points3; -typedef std::vector Pointfs; -typedef std::vector Vec2ds; -typedef std::vector Pointf3s; +using Points = std::vector; +using PointPtrs = std::vector; +using PointConstPtrs = std::vector; +using Points3 = std::vector; +using Pointfs = std::vector; +using Vec2ds = std::vector; +using Pointf3s = std::vector; -typedef Eigen::Matrix Matrix2f; -typedef Eigen::Matrix Matrix2d; -typedef Eigen::Matrix Matrix3f; -typedef Eigen::Matrix Matrix3d; +using Matrix2f = Eigen::Matrix; +using Matrix2d = Eigen::Matrix; +using Matrix3f = Eigen::Matrix; +using Matrix3d = Eigen::Matrix; -typedef Eigen::Transform Transform2f; -typedef Eigen::Transform Transform2d; -typedef Eigen::Transform Transform3f; -typedef Eigen::Transform Transform3d; +using Transform2f = Eigen::Transform; +using Transform2d = Eigen::Transform; +using Transform3f = Eigen::Transform; +using Transform3d = Eigen::Transform; inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0) || (lhs(0) == rhs(0) && lhs(1) < rhs(1)); } @@ -101,7 +101,7 @@ template using Vec = Eigen::Matrix map_type; + using map_type = typename std::unordered_multimap; PointAccessor m_point_accessor; map_type m_map; coord_t m_search_radius; @@ -439,11 +439,11 @@ inline Point align_to_grid(Point coord, Point spacing, Point base) #include namespace boost { namespace polygon { template <> - struct geometry_concept { typedef point_concept type; }; + struct geometry_concept { using type = point_concept; }; template <> struct point_traits { - typedef coord_t coordinate_type; + using coordinate_type = coord_t; static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { return static_cast(point((orient == HORIZONTAL) ? 0 : 1)); @@ -452,7 +452,7 @@ namespace boost { namespace polygon { template <> struct point_mutable_traits { - typedef coord_t coordinate_type; + using coordinate_type = coord_t; static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { point((orient == HORIZONTAL) ? 0 : 1) = value; } diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index 849cec30a19..087903566c9 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -77,8 +77,8 @@ protected: double getPx(const Point &p) { return p(0) * m_pxdim_scaled.w_mm; } double getPy(const Point &p) { return p(1) * m_pxdim_scaled.h_mm; } agg::path_storage to_path(const Polygon &poly) { return to_path(poly.points); } - double getPx(const ClipperLib::IntPoint &p) { return p.X * m_pxdim_scaled.w_mm; } - double getPy(const ClipperLib::IntPoint& p) { return p.Y * m_pxdim_scaled.h_mm; } + double getPx(const ClipperLib::IntPoint &p) { return p.x() * m_pxdim_scaled.w_mm; } + double getPy(const ClipperLib::IntPoint& p) { return p.y() * m_pxdim_scaled.h_mm; } template agg::path_storage _to_path(const PointVec& v) { diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index c393eb295d5..6058fe192f1 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -806,8 +806,8 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o } if(is_lefthanded) { - for(auto& p : poly.Contour) p.X = -p.X; - for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X; + for(auto& p : poly.Contour) p.x() = -p.x(); + for(auto& h : poly.Holes) for(auto& p : h) p.x() = -p.x(); } sl::rotate(poly, double(instances[i].rotation)); diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 7308d7e50cd..6bc334eecff 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -273,8 +273,8 @@ std::string SVG::get_path_d(const ClipperLib::Path &path, double scale, bool clo std::ostringstream d; d << "M "; for (ClipperLib::Path::const_iterator p = path.begin(); p != path.end(); ++p) { - d << to_svg_x(scale * p->X - origin(0)) << " "; - d << to_svg_y(scale * p->Y - origin(1)) << " "; + d << to_svg_x(scale * p->x() - origin(0)) << " "; + d << to_svg_y(scale * p->y() - origin(1)) << " "; } if (closed) d << "z"; return d.str(); diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index a0f1924603a..11fdc6e9cbd 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -140,15 +140,15 @@ TEST_CASE("boundingCircle", "[Geometry]") { PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; Circle c = boundingCircle(p); - REQUIRE(c.center().X == 0); - REQUIRE(c.center().Y == 0); + REQUIRE(c.center().x() == 0); + REQUIRE(c.center().y() == 0); REQUIRE(c.radius() == Approx(10)); shapelike::translate(p, PointImpl{10, 10}); c = boundingCircle(p); - REQUIRE(c.center().X == 10); - REQUIRE(c.center().Y == 10); + REQUIRE(c.center().x() == 10); + REQUIRE(c.center().y() == 10); REQUIRE(c.radius() == Approx(10)); auto parts = prusaParts(); @@ -616,7 +616,7 @@ TEST_CASE("EmptyItemShouldBeUntouched", "[Nesting]") { std::vector items; items.emplace_back(Item{}); // Emplace empty item - items.emplace_back(Item{0, 200, 0}); // Emplace zero area item + items.emplace_back(Item{ { 0, 0} , { 200, 0 }, { 0, 0 } }); // Emplace zero area item size_t bins = libnest2d::nest(items, bin); @@ -661,12 +661,12 @@ TEST_CASE("Items can be preloaded", "[Nesting]") { REQUIRE(bins == 1); REQUIRE(fixed_rect.binId() == 0); - REQUIRE(fixed_rect.translation().X == bin.center().X); - REQUIRE(fixed_rect.translation().Y == bin.center().Y); + REQUIRE(fixed_rect.translation().x() == bin.center().x()); + REQUIRE(fixed_rect.translation().y() == bin.center().y()); REQUIRE(movable_rect.binId() == 0); - REQUIRE(movable_rect.translation().X != bin.center().X); - REQUIRE(movable_rect.translation().Y != bin.center().Y); + REQUIRE(movable_rect.translation().x() != bin.center().x()); + REQUIRE(movable_rect.translation().y() != bin.center().y()); } SECTION("Preloaded Item should not affect free bins") { @@ -677,14 +677,14 @@ TEST_CASE("Items can be preloaded", "[Nesting]") { REQUIRE(bins == 2); REQUIRE(fixed_rect.binId() == 1); - REQUIRE(fixed_rect.translation().X == bin.center().X); - REQUIRE(fixed_rect.translation().Y == bin.center().Y); + REQUIRE(fixed_rect.translation().x() == bin.center().x()); + REQUIRE(fixed_rect.translation().y() == bin.center().y()); REQUIRE(movable_rect.binId() == 0); auto bb = movable_rect.boundingBox(); - REQUIRE(bb.center().X == bin.center().X); - REQUIRE(bb.center().Y == bin.center().Y); + REQUIRE(bb.center().x() == bin.center().x()); + REQUIRE(bb.center().y() == bin.center().y()); } } diff --git a/tests/libslic3r/test_elephant_foot_compensation.cpp b/tests/libslic3r/test_elephant_foot_compensation.cpp index 4e340c37a4b..a1c23f4a970 100644 --- a/tests/libslic3r/test_elephant_foot_compensation.cpp +++ b/tests/libslic3r/test_elephant_foot_compensation.cpp @@ -26,7 +26,7 @@ namespace Slic3r { pt.Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; pt.X >>= CLIPPER_OFFSET_POWER_OF_2; pt.Y >>= CLIPPER_OFFSET_POWER_OF_2; - out.emplace_back(coord_t(pt.X), coord_t(pt.Y)); + out.emplace_back(coord_t(pt.x()), coord_t(pt.y())); } return out; } From ddc8e0d7de992312a43bed7b0940466549f2ca49 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 14 Apr 2021 14:25:25 +0200 Subject: [PATCH 067/154] Fixed obvious bug in move operator, discovered by clang lint ran by Tamas. --- src/libslic3r/SupportMaterial.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 1242df1ea5d..08cd04b909c 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -3186,7 +3186,7 @@ struct MyLayerExtruded MyLayerExtruded& operator=(MyLayerExtruded &&rhs) { this->layer = rhs.layer; this->extrusions = std::move(rhs.extrusions); - this->m_polygons_to_extrude = std::move(m_polygons_to_extrude); + this->m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); rhs.layer = nullptr; return *this; } From ffefa3625e5d16a02c2fd57a6b2bac8f4922aebd Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 14 Apr 2021 14:46:49 +0200 Subject: [PATCH 068/154] Modified version of automatic downscale on load of objects too big --- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index d6b2cff8eeb..303ffe92741 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -57,6 +57,8 @@ #define ENABLE_GCODE_WINDOW (1 && ENABLE_2_4_0_ALPHA0) // Enable exporting lines M73 for remaining time to next printer stop to gcode #define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) +// Enable a modified version of automatic downscale on load of objects too big +#define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e0a5031c230..a3d30d5f8d4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2407,6 +2407,29 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode #endif /* AUTOPLACEMENT_ON_LOAD */ } +#if ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG + for (size_t i = 0; i < object->instances.size(); ++i) { + ModelInstance* instance = object->instances[i]; + const Vec3d size = object->instance_bounding_box(i).size(); + const Vec3d ratio = size.cwiseQuotient(bed_size); + const double max_ratio = std::max(ratio(0), ratio(1)); + if (max_ratio > 10000) { + // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, + // so scale down the mesh + double inv = 1. / max_ratio; + object->scale_mesh_after_creation(inv * Vec3d::Ones()); + object->origin_translation = Vec3d::Zero(); + object->center_around_origin(); + scaled_down = true; + break; + } + else if (max_ratio > 5) { + const Vec3d inverse = 1.0 / max_ratio * Vec3d::Ones(); + instance->set_scaling_factor(inverse); + scaled_down = true; + } + } +#else const Vec3d size = object->bounding_box().size(); const Vec3d ratio = size.cwiseQuotient(bed_size); const double max_ratio = std::max(ratio(0), ratio(1)); @@ -2425,6 +2448,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode } scaled_down = true; } +#endif // ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG object->ensure_on_bed(); } From c5767a22467052b3f7dea9b9f32dde3a48ea5bb5 Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Tue, 13 Apr 2021 19:23:01 +0200 Subject: [PATCH 069/154] creality.ini: add Devil Design approx spool weights by manufacturer specifications: https://devildesign.com/download/PLA_-_product_card.pdf --- resources/profiles/Creality.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index ead535aed42..c7379158304 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -637,6 +637,7 @@ first_layer_bed_temperature = 60 filament_cost = 19.00 filament_density = 1.24 filament_colour = #FF0000 +filament_spool_weight = 250 [filament:Devil Design PLA (Galaxy) @CREALITY] inherits = *PLA* @@ -648,6 +649,7 @@ first_layer_bed_temperature = 65 filament_cost = 19.00 filament_density = 1.24 filament_colour = #FF0000 +filament_spool_weight = 250 [filament:Extrudr PLA NX2 @CREALITY] inherits = *PLA* From 7428926a28f64a9e4e2fb2277d7daff4dadf7e23 Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Wed, 14 Apr 2021 17:56:25 +0200 Subject: [PATCH 070/154] creality.ini: add 3DJAKE ecoPLA Matt the filament is very flowy, which means it prints well at lower temperatures. and given the limited part cooling fan on most creality printers, it prints less well at higher temperatures. --- resources/profiles/Creality.ini | 70 +++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index c7379158304..80c76932d07 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -21,7 +21,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3BLTOUCH] name = Creality Ender-3 BLTouch @@ -30,7 +30,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3V2] name = Creality Ender-3 V2 @@ -39,7 +39,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3MAX] name = Creality Ender-3 Max @@ -48,7 +48,7 @@ technology = FFF family = ENDER bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER4] name = Creality Ender-4 @@ -57,7 +57,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5] name = Creality Ender-5 @@ -66,7 +66,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5PLUS] name = Creality Ender-5 Plus @@ -75,7 +75,7 @@ technology = FFF family = ENDER bed_model = ender5plus_bed.stl bed_texture = ender5plus.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER6] name = Creality Ender-6 @@ -84,7 +84,7 @@ technology = FFF family = ENDER bed_model = ender6_bed.stl bed_texture = ender6.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER2] name = Creality Ender-2 @@ -93,7 +93,7 @@ technology = FFF family = ENDER bed_model = ender2_bed.stl bed_texture = ender2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR5PRO] name = Creality CR-5 Pro @@ -102,7 +102,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR5PROH] name = Creality CR-5 Pro H @@ -111,7 +111,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR6SE] name = Creality CR-6 SE @@ -120,7 +120,7 @@ technology = FFF family = CR bed_model = cr6se_bed.stl bed_texture = cr6se.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR6MAX] name = Creality CR-6 Max @@ -129,7 +129,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MINI] name = Creality CR-10 Mini @@ -138,7 +138,7 @@ technology = FFF family = CR bed_model = cr10mini_bed.stl bed_texture = cr10mini.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MAX] name = Creality CR-10 Max @@ -147,7 +147,7 @@ technology = FFF family = CR bed_model = cr10max_bed.stl bed_texture = cr10max.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10] name = Creality CR-10 @@ -156,7 +156,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V2] name = Creality CR-10 V2 @@ -165,7 +165,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V3] name = Creality CR-10 V3 @@ -174,7 +174,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S] name = Creality CR-10 S @@ -183,7 +183,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPRO] name = Creality CR-10 S Pro @@ -192,7 +192,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPROV2] name = Creality CR-10 S Pro V2 @@ -201,7 +201,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S4] name = Creality CR-10 S4 @@ -210,7 +210,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S5] name = Creality CR-10 S5 @@ -219,7 +219,7 @@ technology = FFF family = CR bed_model = cr10s5_bed.stl bed_texture = cr10s5.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20] name = Creality CR-20 @@ -228,7 +228,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20PRO] name = Creality CR-20 Pro @@ -237,7 +237,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR200B] name = Creality CR-200B @@ -246,7 +246,7 @@ technology = FFF family = CR bed_model = cr200b_bed.stl bed_texture = cr200b.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR8] name = Creality CR-8 @@ -255,7 +255,7 @@ technology = FFF family = CR bed_model = cr8_bed.stl bed_texture = cr8.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRX] #name = Creality CR-X @@ -264,7 +264,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRXPRO] #name = Creality CR-X Pro @@ -273,7 +273,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -708,6 +708,18 @@ filament_density = 1.24 filament_colour = #125467 filament_spool_weight = 238 +[filament:3DJAKE ecoPLA Matt @CREALITY] +inherits = *PLA* +filament_vendor = 3DJAKE +temperature = 195 +bed_temperature = 60 +first_layer_temperature = 195 +first_layer_bed_temperature = 60 +filament_cost = 24.99 +filament_density = 1.38 +filament_colour = #125467 +filament_spool_weight = 238 + [filament:3DJAKE ecoPLA Tough @CREALITY] inherits = *PLA* filament_vendor = 3DJAKE From 2e61826e3a2420824b624f5f2746a85f280f0dd4 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Wed, 14 Apr 2021 18:48:06 +0200 Subject: [PATCH 071/154] Add files via upload --- .../profiles/Artillery/Genius_thumbnail.png | Bin 0 -> 42135 bytes resources/profiles/Artillery/X1_thumbnail.png | Bin 0 -> 36381 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/profiles/Artillery/Genius_thumbnail.png create mode 100644 resources/profiles/Artillery/X1_thumbnail.png diff --git a/resources/profiles/Artillery/Genius_thumbnail.png b/resources/profiles/Artillery/Genius_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..227f7ca8a57c11da579439eebd07f9adf50079c3 GIT binary patch literal 42135 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 T#_|TS00000NkvXXu0mjfE)IFE literal 0 HcmV?d00001 diff --git a/resources/profiles/Artillery/X1_thumbnail.png b/resources/profiles/Artillery/X1_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..4aa3a0dcccebc7aaa852a8a82eabd9940a9915e1 GIT binary patch literal 36381 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%0D`01xkydjCR>yIBl{-U7p zCcPY&oUU7;2Os=&(^|eBMAC<(_QI7wwFTh^U@0Jp;RFGm2l70SkZ{8;M5B)AnGL`K zp67v?A%p-CgFsMHL7N=WxfAFEFvcMAd@L<47BRLlAQB)A^qx5l@yr>_RK9@V;PuE~ z`5FYX2jB!DzU5X7Kk?^OuY~UG`7@1;_3eKM;L(YPp4fBwXKHV6PlM<{7*^Q}tJI&} z4}<}TB;Xh%1RwzCDX=F&WZ+u|6?*Uon^3-oFbu(tp`?Iw4#pbzzK=Z5ksAwR41yql zlnTa}!aF+$Z8RJh5CrxK(#CL4KZMFh|5r>s^?5Xv1FpFVT?lx7Kt#T`zP9zcKlnF) zP^SJ7^_(d+f#`5x-^Ix3X_zAxcPg-Rs^`9Az}XW(_W;Rg!U zv;&57@i6tO)%xc?_`&yob>g8Xc9DJLw?0D6W}_7ZVPvd3i50`B)4HtuTP6sOXfFj3mz?N;q>l zUIh@85Qr)PvNS=pR>5G{$8a!!=Xqm*6A`pFSY2I(u?CeWLaQ}})>I4i`ZU_T3`vqf zIS)b*!XPBi^N5JAjfZJ%Wo1Q706j4Pz|o^eIM+EDt9@s!AO=v0P>Dj!R6RtVL?kUL zf>95B%=iY17@-odPN453a%N<40!0FzkRVXObO?eJj7F`3EE%9)4-vwK9}n{y z^9!53n*jO}d+0E%kW#A7u3#FZ?;{LC_)5Zg0+A3HW)6-CS?8i&*|SOtJ7 zCb>PO00M}p^wdDDTE$dr3c1da=NZ5ZV+?FrO)L^dg4P;an;jUU{RL_h9{Lg*5lAX2NXeRZ4g`WV7LF|} z1hf@c@PN(KAPg+5QD72?SwK5LkOce?+F0bqfRqATgOS%1f*_l3c?Kyx zICk(n56-!2Y?)4jD~O;IWrRD5J-SIhcOz~0LC~tA}})?5p-CA6%M&EKv^jq z0eX}W4hKUR?LZ_TWKl`?;_fp>>LxnQ3^w2Pq|(0VxV9khmzND^C$K zno*@P^BceMn=}FR#6}((gTcVXandU&X94IGI!TaQhlCkJ?GX7s5=(_R3^Ob{I08}& zWKjc3NTg}{ted6qydw<`YYqIsF9Gbq_kBcBghr!*FbpAugfXUMO917mapDL9*cIIN z+}u2jF(4uz#LYBK>Bx~IFIZ4dLXJ!8p`nywFcKoLC4!%27-lJ?R{@icJ~Nb1$gDx1 z9X!Fv8DJq{vm9Ba1tA2oB*BGq=P^G&S7>j50A{!{gvgQQjsF`7{ zMW@q2JRD+rs)d{)DWZDD(R8-soyN~to5 z6c9qf7+W?u6~N`v(;IEdQ^kYlc_0#=8}r^1Ku-*X}3BY|~k130p(+t)TSO7_? zky;Cu9$ad{I){dV3qpuAMXo{&Js;W_)M^zRIIxV9aMq#{N`w_3>uVceE_$jNT!Pv`8v|<{gdq67f{*}ctdTbX^uz!n zqA(1GAlmBo2MFRJdBZ+32*BkqP9kOp2??F005gPTIO)M^jhKAIQ_C>c!1EP?z{B$L z5>{7N(eDl5c|Nq(n46nJx7RHuyqzti4s;H!HMG_cLSnWx1*IgE?_(GzP|8E#1><;M zYl9-!WJpC&Qd%iq*wya|p!dLPwWtRYYal~N28{kTa+W}r!wQAWi{Pa(WSS!<2{r)W z7s;R_3}>%KztaJcD5P>3*uQ^2`u!N&TU`(&TGP`IBv7l@F*iGhG|Q0Z8qPW>Pr>s% z%+1duO%rswJ$&u)r||!L^b*Bx_SHL=pGiT1h2?5UwaA5yFR2y|j0L#ns*lu_6 zzyqJc*S_{JrlzN{ytEHbJ$(}FTp3~r#Bs7!t;T%h$dL)4CpKa_jxvDcM}OpPPkKUS zImOc7zMwc6fSXegaS9GX{8zVD+F25`-J zoIG(B4g$LL)=?BCLdY#aG^rF$>|*=3|J(mS3-b$42w(P`Bo5b~fZuP!DFx?JxQqcm zgz)N+!oagJDA8a)0P+F)K^0kZ9^370M9480Z$bU|KgCmO0fSB(k`l~K27&s6>p68HKva_xWNA36A`JM@& zCw8$t`S@wfFU)+^Q$d>$klZ}M{`x8?cTmy;B?*`bWGyJFz~(8~8jx`yK`_Q4p1TsA ztsY`4(AYi&FE%zk;8R!=K<|MaK75#7{|~;$E-lS{LP}vs1ROs75LE9RoB|*i zl%V`+^It*o%oCWcH*o0o@51_z{1bff zmH#;&@ebkC`5ufZM!Ota)EW%{4k-lY-thehzv~U?^arQ}Rdjp(qL%|je@L_0;H|AK zp^Z7=ob3TG>==47Q@96q|NZv^0KWhG-mtO0zWI(}JdCK{hHEWTwtNU650vK+B*C5s zQSo6qUC4?L$po7XKu}=va80_3eap*;LJy+X$L9Q%X!r`vdW2wU3H<{%VEg1VxRQGC ze1)xc2laZrXkwaL!u)rB2SjrgozFgqM&x5{vyJ*x9S#gh;A&W9ZhPq&%+U5$idkZ)@ium5Q+hpo1*Wd z-U1IhXw58PW?>FnTbo#2-#~cbF}PEwaK1l;Gbw5Uh^8u-U7SZH3=t=J(Y)k&a16+6 zuZ8DFm|d8LnOa0x3DBCZff$fdq8e2Iu=9gpqdC=h^v?IZdm>{nvFEnq$B%Qh+T8E^ z;tNs=f(qeZcoM;54}$t#2ut8N16vJQX%;dAkg!P#sx+axTX^Z_!%!$DQhd*Y%Pkfj z`!t5_A-dfd+DfGBTR3EU$PFV)5`=yTW+3$f)ULlCN)m!ICvnw$6PgvKW@aFSf|4HU z%_$^lDyFAeC#q59Vb2dHG6oa-M*E{b{$IpYW9dh~{H2F~{q&hL->-GnaLjP*h|>(| z;z3a8gPjE87;3r+mnJ~J4XY%U(;-$44AAlz>zf_)20a+(04Wd*x80zC8H0n* zJc@<2$6<^?8Yie!0(3UJ=v;jx7H@e4vWFi+@4in$_qLJe8n6s!8O(sO?23tkRvQdqgfwz9G!)>qq$y?*DN+uK_&7t)DJ)nh3<4B`ZO;|Wxs z{37D-_y?dQ1B3_H-hhJ#k)_ba;Vq3AORWY@ojipkj?wOPics$QsyD#WmDl6cXTFHmSN<9%XD}550)h(unHd-iW15|vO&r^g zy;!dQ1kjh*n3;RM-hvQxD60DOy4QWDxb~WZ^p&qXf=;i8&wTc)sDAa6NN1PfmahZ` z9R#f=V1}?>9bVkQmyyFg{SaU+07ktUL3utZO2Hb2fW=J@{$H4CM%eCkAW<~#lT_GT zKacur-iUB^2K@0qFJ>C4h%JldoLsj31A+*{;lRalXkRSXe-d)+0mE_dx*M+9+}wJn zlwRfb*S?bGXBTKN>|v@khesay8jue`y*9G>E5XWx^r+ZT*j)pW#OcIAs5;WqPl7CC zZe|`q5CC8>0gf1+AjHE2zVe`ig7hVN?KZaNufz3k{6|Qh`Wh;yo&b{oqv+QdO{}=G ze?thcb;biD{E4^y*vDS^%2&D(Lo)&N#0qxf?z^bl?k#mYogdxU*o?9yp>!D2lTVz$ zxeM#q+}Z@3g&M2_Ns4@SKYT9$CwT+eI6Zpt<>(^^J}7r>w!;6HlDR>iG?h(~R2P4z$q#F$77}PJRhOdB|@2 zL68UJgC3;k0eTCUkImlP*9cDs#Qtu~hF1%va<(9~4(09$%(y;XT=5^_xJ*&zr2)H~ijxNu>0 zrq>1PV7yL};~IJKmWHM1p$V9vS1LRHE$% z{wHt0{r2pI9Cl0qy$ALefALZG_P4+NF=jWlz1{u}Kr)jP%nqZ85N3cxA+4)6o`$&L z+h8`fU>N`$gfn2Bg9)&tV6{Ul*+x{YV0342shY&hs74X$%?3Onkmh+2BcqrrWajZ> z1m=-hYcUw~U8~h}Bj0@hi)`qo$B=YQ^JabR&8=Q%+C)Z>UwKMtuA9cqZo!AO?;0$G{ypu4nz#41cZ=*=X)Q0^PAuF)azb%JMO#h zz87W=GlAY7dJ8G}+itmTV`jGg;a02J5mLAy2rxfChpVo<3UQL)|Nhut;?Mr{qd0kC z71Wr4KitODQ=dX>dlk}V#STLvFga{A=g_qwl<-ii*T-Pq9Zs+t(Q<{7*vK1?H1EdV zyf`q%;g7Xe3Zj+}l3vI%1``jxw+SL%^{QJjGd;bwzOnhLEY$~Zy7^{IO-)g^+eP39 z=ykg2cH7952N=NHT7?uI!r?j8;x4Wc4$S~)C}5fmg3cD20Yk{0*m!rMH%dIg1R}X3 zEHnNJkO0bY?;;*20VyTb>(x(0QRPco=eX~_`(BW!p3ruCY{!lrV*pz}`IA5W|88w{ zZ(mzm4XV{hg<%NG7E(#n8dJde6X*|O=)gxH8I>>u*#VN%k6>Rs3sN2k7TP+v>OK$w zEcl{uV+7iAOv$-%M!J&VHI(+K!~cPt+{dX!N7B?1?JQ4sjZ%F3SJ zD3g%md9wZc_uVgq>R-69ig++Y5CpKMkhZmKL0rVUBrK}c2tg1aimIqpt61IKKx%SW zK_E#moP)t&#}_deyF-sM0WE(Pg^JRM#)H@eegIUI#Emvv&N;{YtvC7KyK^=G0KRu+ zMJ)Ed?EB3qa-kpPLRSu+UXcLs>j3@1FWf-KkNol_@9vv;=;z6G z#01btvTLR_gSP0P7k42*g<5~Pc+>Mj_`Z)$yMxx$6g*EM$uzvMvMb5EIF2607LIKh zitx_57Ajokj*60H$Hcz$yT+Jj-gD@2b@Ll6WAf&gI{La9Poba&V>j;paF|229}h>H$pCL)fa%0tuBQ$Oz5oF*bi zL^yi%sM{4f(TMVMj;Tm`1#bO|_tDDxpE}Y_`6uFdyD1#1VVX$W-InqCI@PVFS(Bm} zM0nIG)7pRaz+1Q~9)wL^G`+`fwww1f&p!I8eB+EedI|JECLa2Eu)FWRo9?;i9ITpu{AD3DiPbt*`Fr$=OB%%dt?J|IQ zB3iuv`&OpD?Js`+;L|71ww$(faA}?-2p7(ur&Fg-qSCBlc6OGh>J2m>|GK#KmRHml zU-sI;pFMHG-}k!jt=3v|HwMYlXY-9S`6T3cf!GK{tybet7Z&E;$jmRZhM<&S5`-j4 zILkGZ^10xUnE=peSWVDO?gG(*6k=Wo(GY@Wq*OI#YJy#q=LXK%g7UMa zf~hgEaStl#fhm9!fQ5ij1nU715>_bELgI=8*TRNjoUVQSzr0-z_5RQNt1sN~!Qbgm zV(1qp(}55E+OK}$pWjB8S402+AOJ~3K~(YI{?zvN;0KxQHO88&yTifN!q8GpMHQn# z&RXPo211G;2*x`JM{AMm<<=|bin4b+9OA004*&#w@rz%A=Sc*&Q; zh2>45AjC*3T7e49(F%xvJkLx4P-kI`VPg!OW#qYr)|#_CN1kV#=Nh@z(8iD_C9b@3 z9~>82XFMi=ex9sMtVl}v*uMP> zU*F#Dgo9xcW!6-pia%40qAQmdXRlA<<6k{~ z&jirVTim;Hq-c%LvO+VN9eL>x&CcI&f8VPpLx^Md{;C^+wPYAZEC4YxA3Ju;0l+=? z+(Sw!IUEi>rIgq0wLL$OUQ<=P>FKK^GcqFD^gKz-*z`Q_!q(Py)ai7lqe=yy@00I) zP|AlOQLNkI0zJkWWOh-b7*Z~je~VO-qj*VKuQ521PLJ_ z6_F4Up68cB>0+FemYsRy?~8tHluL`sB$EjYdvTn85j!l9n2eQ2okk%aL`|h+?eO8do1X7S?06c$bQp%SN~tL!XbzxhA>1@G&9Ji#V_l?k1M3`|9e{+u zbZZ8=&XB|jI-L&Er0}p=mLe&EYK#W#&bC=Ws)(h9fDjC&B*HK#7Y6MBRh284ixRm2 z1eZ&@DtmTK?H9}_@7hrR2M{$l#k zmwe~XzugaS(5YS|5C#2iG|UH0Yx5>=4EqoD+UM`Q_d`##pw}JffYxTt_k${ch)7fc zj+F9h*1FI-6DD!uX`@R)a>IF^L+6Hd?nrBmT<5HH&c>J$sD_HC|=`Bwr+6w8{4iuVE% z5M`s2(IcCNT3q(Y$wHY%%6F~2w9bQ?XV@HqjTzl=!$tV8(ujFeN z!k5g}C?ep@iBoRB(R}^l0yRQ4&%$13oW4D?)>=a<%e2U{l(RhNEXz30GURzqS)P&B zMT}}q8JoJIYe*0Y60!s%K`B8}D)^oc+I8#;5@i=%!Fr7N-~vwsQ3Ceatjb8$i%KBw z^nHxHs|1zB;!&60h*5dA#vL=m*LC$rYxnLc<8-09&KS=JNLRnk3Q;MSl@QbFTYWGeg*CIb3%pz4U#H<@vaKL=mTLfI^=$UF!bq{s;c=Lix6wuX{k zA4)2CDxffoE=r&TiE#{DEZ`}R=rZ~&K#XyZ#R#DUzGz|3?ytLhm{Eofg?AlUG0n}c zIOeUj(7A>q>vC&u#yTy z#Kmic*lBFqxt7J>9T$kGKj^WwhJ_$FzC>M66F~nhJT*v4$B?e*eP9S!0+=n75LlR> zgOIB1Hx?IzkQk!$oD5s)U=(Xh3UH1;aY#WR^2+dmE&}i9oj6ylcr(@@&oqp6<;vp% zlm%E_LA6;2H)?Gw-_K}2VewVL_W}g|sH{G|p_qtB2;wrCWD>-qe@_U(N-0uG5fKR~ zB@q!55m{?7==F;mifCMcbdEqQ!Eobv+Q7iU?rFRA1kleNdWhnxmk%C+gdv1O5K0Is z0NC;&$Kik&#kE9W4##~H&)yL@@~FD>npzh}ywL{QXc(;vPT0Aka}EGIhLq|efJ&+0 zSBg_%ci)E~!Fbkl2`Q8;Jh5;>GNV{!#LR5CxERJ-(<&f0fLBL1JD$T8p`N?-HGAH<6<=O>TKNv(9a*BnaPb5Fu3d~9(_n)_<;{W-k8&6 zIDm^O5Niq6GT1p7o0J@I;Ze0NO2^jPi+Yqt3ExgLl0X<%AeCfKd1XPPNE}P^O|X}ZeLF@QB@PQoxerL~SL zmC6Nc-LP7%ZYyO6p66wE-+ecK=z|~pwm44y7=%U%?wufdhszu5iHv$vCLa3vjX8=B zKnN6dj8RCTU<|Oi(T1}E#^@5D1(-){z;1vRtBH&1ol*)^aKK!8LkK~m=AdzzTS_7! znS_uiLIJTWT%mDIu86?_rBu$$Ie@khB4*}wW{!zy!&)0#Yqzp2>q;rN;y4*rD&Zi@ zvVJwH4z$r--}e)#M9&X|{=ktBCV$(@bk|*XDL)7TDP@3Nja1HY*<(G5*~Szgf#62( zdjja^0mvAijHBm@+=a`d(>aGM%i*b_v#s!+1x-Z-H_I56iOb`C1lY38lcW?Rg(Q&R zf_(rv$2{)KXU!l6GaCRs=UfV4T`6zN7_;g7{(7G0ZKc#%=Uf-Sx-lko&UJ+lmWWaz zgawc(rK}JlS4x?=xjA>o9e41(_uea2gAXg&ZG1NgaEA?LU}ow%eKG4A9mvR${E11A*k9eai837~g-92!M>ES%$=VK@|=YVm`E zlx06d(FM0DC0%gNCf2&1)@kmXTX)V40BnR|7+Y)CeBbX8(OMMM`qtWx=XryreM`fi z`RSkeTYa=ED=WmzLNh*<4DwV$P`+90> zYWS{qy~}=+xRZ__KhDnq)=`{8C5;9E7;SYez2)DlN$hfri?FkXsC@f0Zen^C!vP|4 zAR5=OCV<{U8y&HMAA~5q-|l0SsF;o}+9?I9wJ_VafAOnXmOeRR5{xmtwYBw4f%j}M zKgZGjf|3tl=ZIZ#w(&Sn9&OpWIFuU&(--ma6F~31RVtMxGm{|V9Tti&3Oxi#_@19C zC6c@Dy2o6mIR~@0L|o8y%e2-x2%`q!=N^q`5P=dRp)c!1 zx^Tnp+4LvFm1S@GCKr$<4gPy&qwy%`9F843=Cn4IQM|`^b{xo~(grg)WM=>J_0L78 z9y>Sgp4-haQ`zgXtFQtkRT_q&OVYulIccwKZgxh-NveD=@IU;UAMrLfJD#_FSW~Zc7zEbM4)(2#E#DLhH1zr?&@QybqG_i$Ip5ncVqc2-P-}CgTP+KM#V+ViZL|wOV!cdVO_%Vfr1$W*;Rg`KD6wzTfYo-Dwv)|Ew+d{gtI( z=F1w-j>i+U5F_et6l6TRhrh(SB;pC6FM(xQf)9WAx33cL->IY@O5v$OPF4!fmtTC> zFaFo}FD)yd8_uzF z&VgY`P~a+6^#}k=H5;dV-|rfm&KH0!d)6JGb6pI0mVFn+q{n4UauC=#CmcHgDkmgM z0PhxE$1XVo0$7Mi$Z-jB7p6Sd)!{I{vfJr#x7Vd!ugBePpZfit$?|Li0It8`U@r*# zO+l8GWEP?r$0w2iiBg&trF;O5Kb*^v;V3RF5ita_W5#Gr#*UmpF(^T!APo$5n9LMj z53Y3hzuFzd?AVgEj@UU?(ie+*S^_|p>tYhGFqX`)u4qi6((|w@Mx_hyefbt! z2?XarJdVD0_-1B?W8O&)d7LQnB;>dh7oP~uI?l3``|)6$z_76p(9#&&0D$3ONC3+n z0r5hFWt_BQG`|8OqF6|Id6&Km9-em3r+i~5T{I_^QesS#kmJ%=6jgF|uIF-1Ft*Ty zbKpE@Xl<_pfFw?YAeMj!K|Ya?P>LZH?*ev_)cd}VFz_$i<|HD5B@0Ikb_5AG#%}>_ z0l+~p!|izKJ)J4MYyf@q=uua#MMGoU2?pStV`nYE7R*L$Zgy@5fZXK7r5eFV)DU7M z@rwn8e#)<);GANas;%o9ivL0zP0Q7(WVxt~H)g#aKGLb$yw<^FxoOG?q1ay;eP z%}F#CeY^?rL&u<%)>@Qi>UL4pe=ip*uve4c zmldE7A70_($L~42FhBoi0BIqFBO+$!q;sxzVQuw?*4DOb&JvB(IJqnsj3=3~(-vur zae1EO@-D6v!W+&x-cf}@v3C(8eU4JEga}ET=ytokXT_Dvl5^UBVClcFR>RZmEEOu9 z1$MJk-}cl~r(Vyzqpu7G9NS{Dxe%2YgPNHgJI511mjnkti`3gS;xp3e(8jnlO)&xV zCG*mU>E3(qy)eHx{b3=;O$N-&!kTP$ePiS2GG`)YW@lZg{$n8|7jj>_Rt*ah?ouD0 zM~)n!qeqX@u4R{h&*SKk?Ej8>=~z>S9YD4OuN>D41?^pR^|Ax>v17;h?z`_6g7ByH zdhJWjI@-Amgzc^NE8{qxqn#S=&JJwanI~j0qG}Y@8+9>~0lO5Yl!s<>iki*lw+!f& zm6hlH^DGkUo!$)|2^(PNgyX#*CEU4wm*XKRgy6-+`K=^Lf4A2k{+_ig%I-M;EUjfo z8>+%F%yv3RG$x20@ z6c9g$V9Bempl?yWLx0_t-@Xa-(j$Vwn-`p86&{YkR=n%8Oi< zfIfQkC?7t2*jB2+r^AZ(_@JNOz%a6Cg&a-Ydl&Wg?x?@f?gjC!JkKx5`FZ&8Vajv8 zoag%AwL8f^A@Jqp<>gOTqQGk7L>{!R?KoPRUYZ}wH0s&;!B7rc)9Q(zc~68u+58j# z;XwT92X5TIH22K>+{`BdeD(RbmSmaWm>d_LnZ^plybHuMR&E{rGXeA-*tOSQb9!xc z>mg>`$N-P1I-poTETy_=`Hd?D$csFXIM-SxNlM}p^!FY+c8q`d-S27j`|+YDKD->%E??O;3yI1N+sI7lgXjbd`nKT7LD_!5|*mI8Glr zvU2nvA33`6*q(eb-s%?)Q6kdH?(g02mI3$g&K+U%`hy z^nw2*;QZe2{hngxz!)o?vlPd%HyjSbAn?OH&*$@8v!^^*Ymp=gwAOHr;rjtRuXx_E zqe@ia!Ju>9lTY=3rW!R)KmJ5J_OJLa@K68vSaQYLuYU4?|biE{97OS z$ZAx((AO-Wlf+6Hl2RUl9R|G(7-OF`_c@xzaOEoGVo+nW9vR<6C#dv}W4-31M~|`) z!Wm=z?d|pz=`e5PSxOcbLP|&~J{nbS)avy5IC1(ky1gETgCRP-_PD)OGL2SUu+B9J z$?I2KvH#DGA3r`LrK|}dd}}q1A3s)$lcZLS>J6=P=`4G3lD5JiY{khy1c6UkmesQ? zYbd4aD8zTXhaY~p3UCu(MFe7ovX&K*@R+&jd0u$v(8030>frkl`w#5Lfdf|{%~E7( zhGw&dW~)k8+neGvJ-&KjBVJo?>jMs(2M;V9b9q{{=1l8`VrFDXYzZb6!i$+L zD=8hZgAj^+B?Fg>$grW^I{-1>Y9&|*Nl_FDt+mLqjEphlc^<0ODy3;k#%M~CQ~*G| zUMFUUEX&2+cOMH*ojUbWX58HC^=?U2MsYqQXB|Ki%tg&dkbq-{t!@Xf7GNODGBC`} z-foP$00{sk$zbZf`#$-HtyZfxJ3Ctm!%zrWe-t0ak~R36nE&HT{8EB@K`cBdXLHqz$Q zRP1}64gx=)Yu)yazxt~O?mT(+yi`(&An>7-M5R)J6cYV@Z~DZEuYW5%&wxfsWCGRhjGB{JOE8=(uF6VIJJLyd6w4J)^X((S3!^f$+C}_#^F5v zKla`{T9>S<6a4LcPDFgu9p5};=6gAikO_)RB7)ks3#-Ix+EvxnmaOiowi_wC?OHaX z0w(X>o0&qA425McZE6Wxme`6`i@I9dsS8^dpiGiTl1Y;DoA3P15pmAm{l|%jFTU@+ zgjDi9A@7Z~?t1Uu``vGd*s;&=?ETxn9|SPQ6kaYKS(ZUGOrL8dBuQ}2!F#X$LATTE z@7x;T##rOHQHoE70MrnCDpZ*wdC3Y$6@+o?E(ThTpcLJ0B7#ae)Dzw!2ZQj5acE&C z+HAGpa*xqyh&X0~HDIGG-XI%gSZZw3r~mSy*Dk&M#t#Gd$f=c8x^v~U?gjK$sqqd5 zBOVRM|K+cI@VCF46Lfs)nt7V?cre=fwpYC9;wxVE&CSj2LDC-#66f-GJRTaCWiJ`0 znLTp&Frp|b6k!EP90MJqbLTD|B;Y+1oKg$hW36n0%Qm@&NP_boA|7*{IgCd+Mxy~b zos*j9IS~QkLt~PepeiAV#X~gAIWZBuh9C}s89WSY85$x&S(brWv9q%SRi$>TO~%-X z_jcvrQ_e~P#&CUBGAJSuRTYVe9dl`D&y`u{q}DuZ8fq{Y^pR4ErRBxqa{*bJBTYTB zG{r`@tAj894PChDz>Sz{VC@C;Qy2e<$T=USS@t7L<_G@gAN?O`zB5OE{jpCXiSL*7 z3tx7B^oI}G)H9{yF$o00R0??b;YTn(zo5)q1gn-!mQrG+!q=**g@bp|D)&;?4HevFwAMN#AMP1n)MlP7g?zKuWqgFnJ|eAhZ%|6k16 zFMja}x^yAJc+@Y&zOcTrPCMH@jpK+KjYc@%OeJ-!Vk{_v!crV1L~IMw_Hbp69zBe2 zf8{H&xwVDQf9^h zNfN3GM~@zajSQ@5l+IhhQZAI)f~R*bl}l||D-I>fiT$z{5ASmj0i8|yLO)pln#5T&Ao7Xr*n22lYJ zeoi(D#+=q7iW-RH2vKBf-rylKiU^z&cqx6s1){#{>x)X+(ub%*yuf7+qwxSY-qgg> zQWJ%DBBt>5Om@D~@5-A;RpDF?V-%gad9+#`>|_oxOI{?1y@3Ae0WE5cfi*Thcm5oH z;TQfrj3r#UbRHMGOURb~5nl7E=i$D4{v8xta1jUx4<0OLJwrX1E6U}ks?nVAWdX{cc{@$4=%pG5&t z;Nr!La4v(jwz79FEuJf@XVq#2D1*3spXJX(Ksyi!`}glhw>yMz1jGD{bX<2$mBV=t z=e_NAyI5b}#J7CQ^D#Hq#+M#EgBRR#8&V-G?Y{v_%kuy{7MdM2k_2bZpTpx%J_=@n z7p>H-u7n%wKTER==PzCYLolyvP$F!a7)uND_@?zuJbvyx@^K0oXRyg4+{?cYS@%3N zb^s|H5THxJi;!Zo87dtz#g!;uyHqJs6%cG;x26^LFS~bAE$%hjx*`afIb`T+gBu>R zwziILcdPIh53aZ6dno^{b6QGnbP1PA+voCkTD*xo&!DlzGsVE$H31#URH3%oY_@Rg zZ71>Aqi4Vb7_v~G!^RyDF%lbrnK4dNc<&}zxflmPO;mF#s6Z7T*jNiZ^eDbn5!XgGv8K}sGG49H4R?G9?Ep}re@!mnh!6NFs^styW* zXdu!n05%5BJ9sFF737Njmnb|Yni=(MJG=2K{m0ww7CN0d7-Obz^p(Eq!Cil@@i;|y zXMkR}k9M<#9ss3hMwWQj1oU_`hKShx`ww7!V-tV!#~;-^ceHQc5||B~Pm7Bg>a)(d zkckTZhSoPXGh@tDlQ#f|nsFRQ$VOv4{N+cnw6ui9#YF%>zu!Z9ZUK3oBZ?C=8f~m6 ziy)sv#lx$@7BIt0fDBp%S#4UTRxT^EU)`{$f&lQ4GOHIbp>)LsX&_vc^f!^>HEIJ5 z1-@qDVsjY&c3L?(JpsbyR?p8zmG$Qj)6>EHgxs^ z`so~Z-F27#$m{<#vJ3+iw>=sTTMvBk@hFOb?d@)54(YrL9XbjSrJ|zKR8pQ=9jD4v z9D>~#17i%%udl;eg98T+RL1nf;Rtz_7Xj$QCNpB>Y>;~m8Ce9P!3I>R*r3&H(aKU; z$eadQmIYi&1TG$9i~$+0ph)>HxhBh{TxM7XikZo}BWwu30*aX63hSH;Tn+X$Y)-gR zg+fHpZndzuxP(h2eUH{Fn3xNzyeyYc3e%_kmz z@_kP}aq%@-Hiq}0D2_1B93nPoCNVsPCak!?X48_OR83)g1)*~y6!yi)(iDwGV}c)x z>?K}6;z8mmQ5Kj003ZNKL_t)*&f_Hyei|J=g3WiZMGV`F!sZfy*8mHI5C(J$c;K9? zy`;c7hIm1i4xtcOV?f1C#FfZosWTFqBLUMEx*3>k?3fA6lUYe)2~iY*3Z3jc&mktX zG?aoas1+!-}+ta>pL&bvM2G}=iP{Iw~xmjdjtm#?8BpvJb))3@i2)g(ghU( z=q{_Z)-&|f29avb2BNBIp&BTu-V3Y=6_GR@BK8^7J{R5Q9FBNEoT&{`qYB{awF)`1 z`lhQgv}6Wx;dILkW4J2cl{Z!Se5$8xw!;snn6V|P{yOjZ^H1zH{-;g{~it-4}pWK znP@GGFI=~RC38J6#fQ2w2xzxDSeToy@O6^Jh@-fQlh7sP%50wNR>mq-aE3BuC4O6# z1S@zBS2cZ#C#a%Os^?)GJ$@Z-y7d+`=Q`NDbP1pT%x92aOhf4~g2EW`V-IR1x@^po zdl>rZwY|eR7rijooI9~}Lx=w6v-iO^=I|FE{Va$X*I$1#L_LmPe?5*~cLN@}|8p30 zH!IGw3fD5yWSOro)AdYdm?7R{d3hP#ZWm8H@dTEZ7Lle|*c_6CP9HNeC^#RYy|aUa z8HPMq4M+?Kp*PJ6u!e##Ggpu%kb(rrS)m}qEP0-m?@6<448w+kcC!@OPvE8OGMieb zU1%Cd>tt~#JA*_pfwo6f^Xz6e0k{|jE1-ta@MCN|@d#e|;%~w?zvOm&@B^R1aP0!- z;gAX82VV07^p9Wj8hPLU{y~1h$?N~PgY@G7aOdjRrnCL54`>DGPd@Tt{Oa%i$#+5% zI(XP2apDARZ*OBT7+`sM8J)Qf9(nXpFf%%>7GM|P&OVOL1D`}9Jpiqusv!uhJ51Gi ztua8+kqoVD2WynF9GYe0d^qY#k%6_Swh#eI2Fk>0jL|rb#M}s?N%GW5h7+)#eo9{ap*9^Bf-wz5Q4>~202`>h5F8Vz$}C}U%^*=&tE z?YX|S))PUtx3@e17!!F8YC39+ajjNs7{_rI$8qYt&j*8nGP5_v_#{cZs=`{UqtWPf zk390oy^lQdD6YHi2JAbq4}+ZnJ{=#%#c%swG!l!^dLQ|*Z&q1s)AjB73)vta?hHo! zDJvT{eA{)u{F1YOwK-fH?=@Dw+VRf+W|{u@=GVS(Gv0sCqp!LB#gU`U3|xAm>(q~c6mxpbBu;VY;A2Ji4!a>&f(FI$0mcS;CQ$;bV>AvQx%f~2+kbM`zx@91>phd_ z@W+4r$LW3Vd*5X+;PmO!6vwgg-j7s)Gyu!}%EF@dz#*A&w71M6}gv z;_%_adhp=E&p!6pV>?9T0g(5e*4H+j_kQG@li*pQ>fvcH96(et>}5E;@)q29<4K&o za0x$l$GzAZuEWkX&`erj%Mf)w_c=5b%0@$yEK{}#H5Zz&46sV67a(Icdc4*6%y+)z z#{L~|eJkDl_P6Wo#`y2D-*~!@ErtLh)!MGG7&^#Z4sEsZiBEhC7cQN_)=m$m(II2v z@l7`z|LnQ*kB=ba|INF8?HLusc~%FsBy%`%;r??E|M^GHb3Y|g2QPx01cWf1!Gj@| zI&&~43EFi;7_5I0n>(A}LT^GfaEFBrtTD({3mpnV6h&TD{mROUFN5dk(Zf#w^pW-T z?H|hX{77g^dmau47!F1d_V~>Y|0aIuhh9ff9OJ-zQ}^F?3mraw$baE8pGU%->;=!i z^(`a8&S&hwH$I%%+jx}mk$j*hyJs+NHsIq0N;pRZ)o!bupWHw9YbOprS5Ke5TodoB zO>zJIOQ8x?HKj;Fsn8mTS`OkJ2E!4yHhaiZS8Zyd$Ow^@mAh7+adoF>H-=UPCrNtj zrjvjD;DZl+55Xqnorwy*ZIE$hl0sK$xrAVH-{&_W$TNa3kAB5@UtXLUfYhTzJ7 z=4Vbl^rZ*BlmVJdEse#PlZu2)EG7NX7kmT>T5Ux&psf9(XmF@vKom+Hv&TRC=dTk0 ze}3;f-}&jawe?q~!|}gtCDDsJ&7^tm!n$>NP9Cbm{y2X~H< zpFef_G~KzndaVMwoI5NVn6)NNLDVWTMB#5AN_+%0%zMg7U(1oEsnzQAPRKXm0&qF~ z3}OLkF-upen-@(9W`N4>To?)!@!P8E=_`0w4~E0R;?m-u4!QTS(U2NU#JQ-gTGwpw zg6H3GcrJPD4ddR$KYjfhR{!$7?|C=9@hx{K0Gv9#lD=~8V;}9`aN8#y)^P;#7<0e< zvgZ1$C?2(l>C=(&gUz}9dV2NRc_oGn{Z3U?Ke?f6vyBshnOZI;q*<2r2ZOzU{#uUF za1;ewL7v8XQrIR?Ic2StQWDizN#zs;GZ&aT+!of_ea0AD$Ol|`l?WNrojP@@_uhN& z>0aqEFM8q1Sx@ngjE4bIzVRpCR9*0*(V#1ozQJnRVhm|-|cpt2FgNBjTJ3SuJA?*l@uQ4tf@0xeQCJb8eVr*{(Dx zTv=JEr248t98FU7WSbaN{cBrtx|(%37N~jXJd2YbxD%M#xN5Z2)M$6TBA}AYZnGI6`Mb<25<{Iw@~U7 zfL&lDma_f<$_ek8*Z=*F0%v9b{^IHW%@6&H*EQLge;yfYp~}J4sWuSG4AGM5=3SO$ zyA^Get#4CxnWh3&FfArJ@k_p%8Je+oOdXBIy*%7Z+0*r8;LZl~U-%JrjmO->89nWr26a)UI$U zr_YXTQkvz|a9cYuOzr>Yl4#O=oORHKhhJZ5MbGSl>9i{emdRPANx zYj&Z)Oo1{QtjNqnPgwyb1*uV}IwEW*Ng~*Vb?8!MDHNM0S(!=$ETzKEO)$|<3y->* zb-TTQo;i|6BLWd|;6F~OVOEj^02yP*+K5Y;+FcmXTI@N>fnBZFFAH^E1T#~e(f`}q z6kfB#8q+j}S(FIMHCNOE{!K27A*Nk>{)h=Qk|$!X+&v`Lqz?dJ|vt%kuXgQr>;s=??H~4!+Xz6 z6e!MxcVDRwT@DA-B!xK7-L5@XL4G*Ikg7si!?uG%DT648l>l5-If%$b*6OTf^InF& zirCOvgW%>(RsJbQ^{L7>L`2|HC4V*&w#a%a5zDE${e>heXq`xv1mqaXGH(g@O;m_rX8A)I3&b zb)m{Ko`^=ydD@K`y0ESwRW(=gn3+P+B!NzfiRtHFhQ4-7X<%&BF4BarL6ux=;CGN1 z!@E2~7sYz$WaC`Zjh?M)s6G|ZV=thuRzMpHB>u~WYEj!9I_HBtg6`Vst13%l3NP1* zOU3M!(gaNtDP2Wfj=g}s97D%tmacDr08G5Hi>VdwgRk~(Mrh^!udGDZSGHOsJn%XS z^&X}@a?-WU(NmVhGzC4%HZE~7I7_s9qdCYo_|gr4r_Rw8&n-$gx3aRbSBJhv%V=Qi z7ivnX24TvGL713L79}Xz%>i8x)RG<=yb^;0dcm!mgjq2uOGLO@b?Ci-zP!p2DTsp2 zf(diAVGN`2EUdWE%4|ka6zOio(e)wOu(!}D@4FD>m1$DJ&QnK3x|gA^!SVUe-^Yzc ztHlJKm`@eH)+$q0ZpE~>sT{9%<$l8#L$U>muCQ@UKaeNq#(D3DqtU3}@9m9XU5jID zdz*lwu1bDF8O20ZQw!$_W)f?yGiLXm!m?~Pq1To_FTW;xYemJ2R`FL)%49E~i{td^ z(=;BBi^>u~AgJ13zJepg1cs>hzVE$z^2dJcjczvwbkSUy>6jD&t^^XQ;7C>eyo%C1 zdyu1c4ElX?E}trr^6Yd)QHN%x7{J`m{oKFX&F7F%*d0z!_Dsx`Dl-3Mp}U#`D|;Eb zexzwefjt(ci4+>VsHwihR^M7xZB5(0?$($NJaMiXP+Bc+N`5+;FDFOuW$3GQ1n!1V zy<ny6mZ&x~>bNL#I&4UWUHfN2}EqfN~}-RQ)HA zquR@ahJl8O72>POOZa++8;gsJ6XVUPVTYo-T8^!iBIw1hXfL3z%>~NytWc_=VB1Y$ zB)`flT_0h3Q-1YtvXak(Y2pzQ1Rd6Ltm4OGajd`q7jtf zrKE}qm<1yBv7^Fsh0U%GX75WG11%iF0ufro7r!GYJXAeuRZro)41H|^+Iz=<8meY6 zihZEyL`FB%SgG*awb#p{XJUl{az$Rp=_7wY`9@9p1ZAk$zG!t!HAI8pt3K z+12N-XsU2ovYSM8O{b#-B&Ed)5g2>QN!R2kL}MoQku;rkLY|Q-(@bjAyE&-AQ;rl= z&(}IlD=mg;h4!3eugMbXoFgd+>vhM|iHw010*;E@IUWv&W_5LS*D~!2OA0PTcdBz$ zitzwat8OS*0Tu|`y$pR#jx-$`72(Qjp+J6AjOLfsCaP@RNKcchtG z%GGEn5&75uPjA@k6kZz)EhH)o)jIPTDq%iOYqsl!MOvN0}BlJV7iQ+s8*8Q4*T?36;LXmF2Z=IXkMXr_ws-FM%)YcupjtWhet+{xlr zS|e*oQX-I&UgZg&y$n5tp>x(^j8F?fMwN-ZZ&^<*w4$9cCg07ctjxwG+peSvP_a7< zXRb~kAT89E_A>M}$Tna$9#BspQXyz%cvEtaf@u&D$VO2VMK;fDnkc&76f|g z{EoeVzUGmQ3Gl1R(WQ<->Hn=NBB?6U-F&JlPTTF)_ziD(!>%sd)vb{y5F}vdlK%n)L_|pm24*iqUn|Iw z=gvR_If+V!7}Z-~Tn&Q)YV+LL_rL%ByLnSs1oUM;D_63xy3wTbqE{Dj>}BYBhW6eQ zs8sJSY!oIUchyvLap`t_>sv&ML0PFr)hg0|8Q3)WUPQe2-tPtUHOV%Bcws0pFd2ZG zv~`PME|yapS+rWsUEWktsw?=5&RS4RG&)pO(=>IxUN67K6lzd; zNKTpsR7_fEaoU*J8Dsr!K4&5V6)_^BLV#4JN&;0^E@XiiBSKm{=8m_WlD&Yw26%9B zanZ(cWGgI)nrTpJz+UtTy!T^O9lhyIZ`#e9!m_hU)&5U43=&uM=VCyjSoRd4cfa!< z+6(Aw06DC+h6_)HGMI^G4eX1A&a5`F3f;8{paNrI#}NjJMT6WhBxveN>AYJe2@$;M zr{BEC<+v7`!aUDOM5ttjkphrX@LVU(l2kMi>WZ4X@O;%*HzPz;a9z|Zq;R4Lf{g?z zs{0Jmci!`ky$pRVE>N1LEFxSNm6=N6s>Pl#u%(@Iv|GopR##WG-EJd}WAB4JtSma2Qc*R< zJSGF`g4m@o%7(?ctEXqO7tqs?!-$Hh(MYQi{LmT?niFNxW@hYWC)r|1wqPd)@tvvK zGp5_&W!Cmy2vscpBHp=(DJMUDU`UPIZ5X4>5)whp64Vy%^3?)7cNOH}bi3o@5ZK@UZ# z+33}g%QAZN-lp)HWoS|bm7Be(EK~R4qmZ2jgCXta46Uk0L^yN|D{e>Clqw1TQVF3e zob&X)_q}&-Q+SOqAy1^O$|^=7c8(H?Q0ezgS(aV4DJ;a#g8^Kz`{&EW+_ZS?H+p&x zrR5qtQv}UBkx4V9_%}4g#Y>jEtA?TX0=ia!5)oD?sR{xqOcCN+f+>6DT`gcXu-1?< z#+*8J%HMhCoqF%R_fnE1)MzxwT1&VK}y~PG9Wk;hgV0 z?|IK#PLjm_=5PLHXD}EXQPu0LwI>1G4B&cW%s7tYvqZGjXf)0-^JWx97p%40?|=XM zH=E7ocsw3E5pko@$Tb=bHyjR~F~+T|toTcpE@>RcXti2wjHxT-tE!ep@cIuG&?U?; z9GFR7JhnDBF&>Y=#MOCJRcguZO1@zZ&)V)wt6|1YO zRUkb0-~%cGcHXmy*OKEK%1lAge>@%|&mH=Mfw}+w`wz~|&D}LWKR@=~?=!|6YPDKR zBEn^zRYZ_w8LYMN-Xn^lDuV&Yz4txm+zx;pB3e(9WSg0{JDtvkwRWrBZg1Rh!wp+$ zn)aK`=5RP1_5k$z{r;%gY>qZIHikd@vp@T^$G}dXK21d_a%#!{``!?$N@YNoRUq#i zhQk5UG(#MP&+VLps;YA?KY#ARXO@>22UjDQpOpcIb_RpN1`!P(dg!6;L zrwaIx^ob^(f&^8)qSKf6-b*;bjqeefyYGqLr zjbHe}7he3}2S2!#W!c3fNjAsh@e@R}ndkXAW6Y*=ZmrpDjsWCIlIUPCP!W+V%e4Hy z7W7;MJFTs)#aWiw;`3FRx`alhD0(&7BBE)UW?6RiW{d6DYoL~p;B9X^MXEy1Im)sW zXP$giKm1!Ckvt#szI_MnT&L5F;b2@W zXJ)F`xr@J3Dgbo(lHPkFBCD!#IpI{iFLus>h;GUAyu5L=ywTz~1~Y3EMLWirf%pD` zi0m-)c_KQOB*~>HiZ&M(7A}6^10Pt6qG%X+;7Gmm<1EdivuDo2IR|GQvMeh%*NEd7 zf%{U;K&UY$Yc!g}3l}atdjtB8JMN&n@4ovf@!S(syiSeN4@v^0UTA;1ICzTW{!h0s7`HMiVUd5$*qVQMUb%ffH5(MESM*@ zZoz({EJPR>V=7!cReLR7tIne?19WQ64%1xgNjCE{m6z$iEB>|&a+)fi)tpOT1>Qg+adu5m#7Fs}fV5#8GBrdG>Y+@4owP{nTInA$|7#C+3nk+23q6 zkFhbwq9{7X#3%CH9Tn9h-ussKzU3sHhfo{PW^geLS*y%g1$Hj0o>kD6%VfDHDq%!D z5Q}BM3fB7X%Ew!4VT`FRb{Wj&<7+Sivq5&HKrOR+J)_Uw1Z9BL>e`pxK=tREekq!Q zL8<`~)K?S@?yg#Vp7JKp@On0c)pO12Tg<}UEHUB$ z03ZNKL_t)RdZ5?ecl|l9^d4q2effGN=0)&&4vx7X5G$C7*hGb`9l_-uy}8I96~`~UWVpSu3Uu}|H2({;ap+wITi z(Qrt^!9W1GZ}@onY<#bZgZF4DEF(%_S}jWE=BP72Py3dac;C_@ zI*lfx$Rdd?jAa-Tq;88X7g!axys*XmcBLjdqaT5aMK^&B<;AW;f)a8tanS;&smIlW zXtqW>8>F*Pq@GdBAgfGvn5Q!I72rp8LzRD5&))UVQNEY@jm5;((2W*`1>!QKX^O#c zh|R5SoWHbz_460eUAu&FZyThclHrw*8-uvrDa=S6#5;%&pCd_<;`3B(Z?zUglt)X8 zb8|P|dfWc3-thZ2*4MAwTHk!^?Qg&5LvMZSoBcO*K)>;S`-u#|2Y=%i-}@ggo;m*_ z>8|hp+egowUb}eyMJ!e^CZY}K02%s3^tdvV5(r1|#)6U<)a;XqaL! z=wsOLVrP3Bo9k=HH@Co9ABH>}8RSqzM2H&!e5Mk7S6cGZ{?RS;xqMXNzJ z^4@E%tdF1D_-9Xi}Jcd5?wo~+jKl;Y}Cs*$J z`Y{sv{9#H{RQ%wwJs%+Dk zO$aORFdXOD>20Cg>!RQ7qu1+W*zX}9_8_AnkdDxRg9RZ6A@T}G9;uDsyg}*-Bj=I& z;63Oyl-!OTJd9h89mC^KK0Za_0w$Z=a>c1y-XRqrCxhHrC|kfrwajcl$^Z*UJ-lb zb0AJRpJSAc(d!Mcxv`1O%}w;Tw=o)Y;YI@(pQ8n!X#ryiqKecpHpddBfaoqCU+tKZ(==ColSDCUMEbqH|?cF(FeqRk0TN?u; zMB%d=Fqo8dWo6}S;--Av1$4+ySrBdz2z0UwBB4s2-(BKTM zm=GIA#EjTvhzwv!5pi%tO)L=Sgk;+y2Ez+s4U91iM%;jl+mJX0Ckdiv6P42Kx@dl>C(gU3BIeF}m@ zmOHpgET7;A5HL~!#u>qB(cIvmiD7Mlx5Y!9LwBc(JkQ{v05J?143*JocaS6v06nNG z;y6aD*#<`jUwZh)8xJ+o`B!BjenYU+S5{V7CnjsMy1J^b`LF-`pGG5jeJA1h^Jkwhk3MmJ zE>C^rHHVA_Q1yT@h^@uq(jsDJj7KB9@|E9?g@r}rd5(U+kF~8H27@77o+BL(0TqnX z6j?Tc^8%3^UQ&n)n?$36hy*cn$oQ6!7$7k~D>7(DgjN!v(=cd81Qr7Mu)GhI(G$X0 zW27cRMg|jv$A$A3(P(rqH#Y}nMx)UH5vhpK z$&)AkpPO%e?r#}3kK4$sE9vU5A0ufhec6fLS(8Z{SWgsF&!w@=2goQ{k zXMtv7us@2>Fb1(Qas|{y*x1Oi-p>#v31SmU2i^l+$9N($7<+*yKoNlq!I%iF0ue%* zr|>Euks^p4@;ncd*& zihS&V$RW$Zg)@;sr`^QD(h?3GI)rAsfuYNhrYZX45z=&sbUZ-6*T-J1rD8{1^mY4S-j$;^O5yvqajRxB7R`s|t*vsG*FL2I7g`vU_U)Jp-#4%$y z?4#Xo7i52)qM6of@=1~)GD4CM*`;C)MTcPQA&7^Q@ZOEJVHHwUFPimswzt!9-s|r4 zE_DaPQE#i;J9=>89rl?G+*MKyn#si*metkO%czInqEH| z<9ZpV`@QpvjQ#=?3m~*0!YV-mP&I6p4jw*;@7VvHNYk;VSw`M@r0E#xc#O3RmvG|5 z35X$#U0!f>GdSn*$m5TpKNzClAE4XqV$>VKWg#e_u9{LdT+Un+WeZgRIehNmvYK^j zsSsAP17;!+GnjK&YpMoF97Sj}8n79o(P|-%n?RnzPzD8K>^(ppZLwfW6(u##GmKrh zNu4ilR*@@~_3G6WV@wsuNs=H*k}9anuPBPDCQ%v8WsSZ3owbuz|AcQ6%5yGv_2vnf zgZ*8<-w&{_?9&#RIf|l+7hb;St*uQye*8F+qyZbnaJhhqR5_SKrdfuxH_&cxNd0~v z{b4We4|)ej!~B5D-Aw{fBgBhM{agFB!3B^)Nk#OoyY7;?h5gj)^*M9i*r;I|NyEJ7 zy}xk4`TTm0qFY38s~5S+sa`KxcF6mzp?+i`8i{H|BqmsnU?E!D z-omAg4J1*FeFqMJn2;oZ;TWO@?S1?4*tzrA>4wczo@dC@(8UVPaS!nxd7i_ogTNPl z^tNbIqq0+|BngWP$`m4)wQiP7l?FvwsnCMtpJmgH6y{(J?9&F!zGbj?AnU;gkE7*! zkVo!=W}~40L6ZtWL6XOnhH~pI?s`Tp1GfA(iXvoLhD(<&;pow$73?s^R~<)V9K4Sz zHlZ~x}^Bk%Ysy_gTA_7EV!YJ6>eEX)qQ$XMGwztsTcfCUa zpflHry1l%0VQs738@bL;zV)6?>ir2Jf6_4BM9`a6x{xJ$6 zqH?5^RTLT&^o^nzi!X}u-YM4C*FuoEJorW%#&|d=HOPw-)5qDfk0H+;Vq>w~Zlm8* zWC_DL4`xE1hw>LOBjyC29mG#CU_o=5YGh0gc~H${@^ZHPY-g~{Ocm@B4|LCh2WtSw z5D;+j32dc3u**U_AA~3lf0O6=Wi9i1=dk{LnU%wr4Rz{5Bqz^9C$!U(THcfI}RUg%Y> z8)kf6=CUIZ)8Ru4$&rTf2jTL45f|HPWU9`?3j-uz5+$O73s+tplx(?4o74-`3%q)W z^M%Be$7q=1k%u3{IL{H=2vHn?Y!}8FG~xzg8%|QIJ%^Jw+yY}Pn#~y28f@=uqt_c? zXL|?67!3M-7-NuS8N7FeN^Tf_HHNFtvaCq5GSN?r@ z113p~Mx$B56XzVZwzjajwT*VW10t!ie7GqX&N=vEHSu1N=XtTZ2m&iF>|b0Ftkyc<{F zd#{HaV?!Ndq9lo!T2U+ug1evW64iiA}JrWBja^6y8Z5!Tk$u(Z4nbDeo)Zj8CPd3fiiZfIj{sD{KAjYb3B zhij=wap1rq%+JqP8Yh$YYpNnLmEw-1p<`PsFqebM_4iB6B;F$(k2_zn4t?s>DQdUo zc{t3z>&z1ue<^o^>i}p5V(}>WNYfNdNJf3jP!hkCCaWTofgx5=(1Am957?&;=F=2K*6i#IUUu>P z&c98w-t}NlB18~iFzR9R(MK>C50H?6sEMet0B%P}nr*ZiO(aQD;H5z3v2g>&8YI>t zvV0j!Yzkx{7Je#5UH#|f z-<0pEtPR(*`^@CYWqj<}=bW6kum&~^aQN`iz#tT=_A24rPDJF(#KaWZ{bj>sHhGkyKa~x0*FUP)sgD;=M*(V>x2O4j|0~&)bJ&xuV zK81}hd=6_#3~N0aNgH#GHkyq#4jws%`T0dO8f_5UqP!KF7qeX6l0sQ#)Feq@jKO$3 zzC1`uijV4)YG|DlUoT}%Pn$?_A1Fjnm6z(2(emU695{`YOZ~f9SW@RUU-8zPfim@* zW|nO?3uUg{@SS}f1{YLVE;C#0y_JwANs@4`tp$UTW+`k@GAx@Yv*)ZdP$Co?O2d~` zV5aNLtw>f zw;AKn_&h#-{1J5K=MX2&LRt%jT$EPA;j?eNegUH>!r{Y*apA%R3H6uXyQ6z%=of+pogUy!2n-hm$t`=HYYrkzfCL-0=JtVk8+B z{CDH~U-12i*9m{~_!n{U_P+qAychxm(eSC(>+G^Sdo&siEH5vk-|u5KUMh4bEHFc@GW6M&*56v0wb1cjYi(yRBOgv!SWr82!-HbF9rPis@%m%)kIRi>V) zpX#Ql4XuKC62P+oUH`XU^(cQR=ZC`K!&Svpvrz?W^XWk zBX9+U&5iW>jrHw!kJ8~w z4Lf*mnM5$m91iRw9DeRCSX;Y*NBT>+=M_JQ{mn)E=ZDVWZSQ?EUbJx*Tgz?qo_rM! zzTnmH-`d6}KJhvHO7o)-C}LxZK;{V(xKLsRO+M6>Mw;pys{Q--qtR$!G#V9ZMme%9 zL$BAXg1iil65pk1T8#!JNsY=`T*Ojn5QRs`6yHI)fTIepu9(iKu*bx1fr4R<7L-cW z`b0P|aa^6&D!kGVZ9Rx5H$WirUn!txzv>&i`ut=0^GWbYrQb}YI3;Vr>!2CdcDYF- zP&v_AoVP0DB5eAnfJeFQ8lT5BqFIODCt-6R;(CC=T$VJ_1wj@{nwhZX{*KGZG zwlXn`?d$Jl0-3l2Bnlr0K>~@$2R|ST0r86j5;%!1izK#tUXGA5L>87o5I{144-}M-{6qpF zArvRJiAnIxc;b1#r)RpSr@LN#pXY2o&b_y8Ro8@s*dei}q>?31yT(<$YoB%2T6=9k zY6;&qwqILr|6Q-w`xrz<%uKn}l%$)uaOd54=%c@k89%_cj;3gBe;Qavss0FSJJ+;;d!$u|P23kWa4d3^x&gJMc18ZD1U6Pc8qkZ(F2w9pTNfYE* zjx4X^ae|e#q1h5wjN61*il1ht`CS#CF|P9>@~@eVq{IJ^xEtgL0APOb25@j@a^QwEF?#L4XJ z%**q$v(IHw{LR_z^F0O|dgcqy$WA-jwYJ*-i*B#;2@)9*k<8N+QJ&()_B(Oo!#@ur zBy=yr`sD}kD|hdJQYk!o79V`?Yp_WZ7uHs>d*K%_zc7!lzwwW--?$HIeG*|qLBcDJ zD7l4>a!!cK?y|*`Fm8&v&LfX@z;WGiv?J7yU{5;JP+e^seBidCjYeO?8FMnkNrEVf zkR*vK^ejP=rjAoDR5N1?Y}uVL@PGj1j?Xa{xO#?0IpkVCOFFGyz{Q&40z#1Epy3oW zFe9(#{HlEGq4&bJnEWmU4Z@gHR8;%C1=WkTzU8272+m1JrNN77fT2lLjf=UU>56Z9 zX@0181R~KV;x-V*FWrFy2UztwW^rYu_otiv%}v-tle?y#4VWqc%^T7r5Z3F8U z??w2p??H4aK$|xZ`g35Jqn3+3M5~ct3@gesc3506*=lfS>a3}A!}6O zXyMlR(z>uw%FVv-qcJftWYu(ZSXOm!2ayuQaf&345hn?f;%TfwUiw-{RM=Q4XP1;x z7F@oDQhHc4DIi##!)}BDwrvmb>VKU;b8-QJ_A13mQ2AA|#q>Ho`?B`bNDmD=f%^|9GyM6HLm8*|{GAX2jP(?=weE7aXbLBd+<*V?> zAh#Muegj$y^!HrEi5rW^D8^<}U}o2Y=(Rhz^3ruQR_Ec(Fy?sL4W5>YT@ij*7KP(7 zo#%K!L)BJ4jNxI&vI7e<&kt)|8P>~TK>o?;q8Fd3Gb(9;vRj?v6BWHF7 zrfPHQ;Lpo~s|zy56egsiR9s+<58oq*2{*thrG(TTf}n}x$6v?V%{2&Rw)mPy)yn$c zZ7pMNVIIl@x|c$pDcCzIStCqVZa*)qK&@>ZRUh@1*B>QLEL= zCMFuWwT6fYoleIBz^5L4y!xN~L6hNXq7in8Es1i;90VW)$lczMXGWMb1NRAC(SvYH z-l3I(uQgf?X9_n3-t?3QYjXENkQ%UaT*Hl9Acd$Ya05U6t+4S}uDJa7MM1+Dw?7>` zBuI?%z_R9l7{%K|B1d_A7Jg>1b#oYF!e8oXMMUICQd=%;po(%s6=O#5q=)ktFXQU< zRWw2$LYDN{RvCQMOy#`GIZm8Bg?`$``bHO??jnma2q}>!F{A==lS2z#{SL;NrplAE zqB0pUIT!n7M8Sp(f-sBGSQaE0U>KXz;M9`Dj7<`J1HeichNq{dTi>0Xoqn~|Y93o! zTDm#n>J$KeteiXg%x50|)k_yH{aT*q59gU#WW(FiG@Z&VU@Rb|sAS2(az&S7S^Aa9 z?JajjBL##~F2g}-NU4iF2MJvh0LD4k>YQgu9n}k_^v;gk-f9XvCV@cZ<+0cZJH+S0 zsa=%Wg&_#u@=EsD-eNHKOX%>Lor8Lj6e03Q+ckY1adB{?S^^-J)5Q7{&-}j3l*L>(pQs~D+_;_5?u+}m& zvk=6&F=VV2?p`6FB&ABp3reZ)Gu!VAA&yM}4h-@0&rF@2o1H!P4$0yD000Z>Nklo9+PxV;)s5L5Gi*snShmE4LSoC?FNma&7536XLTL@p)9^eWN-J0NLolFpp4vie z5$?Dyx@`#&NE9MbnNe5Cq*PE)fEBg9c_oiYqSy!GaIhZ9Wv&Mrj>^y=s?krVvhl{L zdEgMy~Qcn@f)5Sbjuj(;D50U>3T*H=UFX zNNCr+GFyGYVFf@ixq&5vI8BH)2Zf`g>*OT`<@-K7t*faC!vjONsCPq9rE(2+%u^v# z7x{IltVTTX=X3o>Skq<*Qa|8w|txQZHJVw^s67REB9 zATS$bS&k@*P;AaB!dh}Dr3w{JjD<(NUJs2%1D7vfh3EM-F9jeZV9c<d5hkPx!CtQ2)s;k`k}8A$!Eh$RV>n@;P6XyJ@WVhm0lmoGOOXi{fK#)X{M z-DF&K^|G5woV#=$osBjcjS2Mn9rXKsD5cQr_0VdykftegaXQvj=y5!JSTD5_HA$g0oXolSd~fTSsT-W{y4a#N$^-Ol_hR zVv3^uf7#6SzXr{G<%Pd*tgmg{yMA*$P)hUc;_S-o;>_hwKJrPkbYO{g?A(bz{QW;* z0QlP1zN${0I5V}ezA?A4zA@kF_2&#@H^6rRbZ4I1yOKEBlcwoHk|esY6EX<`ksyMw zMbI`%r>kWPxYZ&9XIX*IwdXFUvJf0f#y}}u?G>a-#p1%pLf~Rmo3$2_#Sq3G=Xk4d zyt8&yrvWj>%G;yZ*U7dedbsA;lo=J1$OexsR)c?BngHM zmakpK^_6Q300;LS*iQHHMSd=+vN|NQ$Zpuu?7&68nO;T{LI2XZ1 zY=EegG^I6yV*JX;gMM=#d3S%xHx<+Vo@N}?htwltU*EQe(t-VJdJD=F8V+d^RV z#wyO9KkuRp3dQhN_S0LL{>QDi7?%7Ed!9z%dk7mL6A>$^g&;y4^(_ciDY-T^)jT>s zKmVc-;-w@>Pqii|`cqTQ{^H_%vb=m9X_CmQfrh)bIBRU^z20y_lxiUD0NIf4{%Ex6|qDjiP9GZt@wEi3w&7nK`tU3*N~A z2$Wo-8+s^&TM+TU@F|3VRvtXh{~D(&MlCnWAuEw%UB#NRW%A2k_4XTA&MfjS}jCT1g$l) zEJGNE$n(slK4lp^t&ta(!onZU)6>)3>2xRv0v1H9wI(41v9*0`Y*#4J_Cx=TsmaM# zo6U)rJ>NUFcH`#S_U+p_2z|S>^fZ_B*-umFebYMhn^4eO@D#I~u5L5}4#R*C9Xgl; zxKup9S^nI!&pz8+U*Fiac4K2tx7*#_>vngU%-#Xm9fo_2$M#XS&~urZ(2sXQUFll-75eJijMN(|h`x$z5rd zZi}Nh%uVj>F@;-=1Q{9_e90JFogSue@d_e%nxLd|GPVv~YN)TEwSzB8>nb4$)+LhK zf<7FFp{)RXKg7AS7qGIjf*@2bZIlO7CabHfNRng-Lzj~|d7g6+_#hIjl(NEAxGYnf zlu{-Lf(y;C@jWTUcatPNI@xMmTv(i6ncud!_UNZRo&Li=eT6Qby@)42|5ODPe-^v& z&rCu82t_Z-!q-Y!Piv8;Mz%LLbi3WrQIhym(~}EIs&^%E{6MeUy??XczrWw#+><29 zRL+o&F%**7N^%oC%PfqUlg$ zWpxb~&tHM>Yo`PfJIiJ9g}ZU%Ms?XslEf|av=+YSvC@i_(o`f%W!h5a`~F&UviaZP zRQN5=^Iqw8d#76AM1N{}I?;j7vn;DN!u~9G-M4i?-|De%-@eMNRnn0A_U$8=E0IHx zfKqCv-|z3~_xty5_WSSJ*x0x)inBe&ngzBzX)IdCnx-)(1T$G?TSEg>NZ<@6&ONzi(3996cov_t?%kl_6X_A}mr4|+SlIk&so z$tNX*dG7h==+QrVwEAVg@WKn;)vL=3opxu(`pu2)8|&?zS(e@b@E(A7WO=?jij!?= zn$6^SP9}F;2MH>-zjBd)LzbB=7{q&Hmk+NxUP8 zlQ7LQfSfcV6hR1Ba&|9itz&pRPoXi2{iERYlBGCw=#Y8lnJ3cbz+PcHfB;CqAUL2haCNN{KwraqoS5|K&p;`rscw`sqhc*ZB_T%>B_TwG$_#d;kQB7{3O*H@j002ovPDHLkV1o4b BwlDwy literal 0 HcmV?d00001 From 815e16930337cd714414d230eb44ff299cb17772 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Wed, 14 Apr 2021 18:48:38 +0200 Subject: [PATCH 072/154] Delete genius_thumbnail.png --- .../profiles/Artillery/genius_thumbnail.png | Bin 42135 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 resources/profiles/Artillery/genius_thumbnail.png diff --git a/resources/profiles/Artillery/genius_thumbnail.png b/resources/profiles/Artillery/genius_thumbnail.png deleted file mode 100644 index 227f7ca8a57c11da579439eebd07f9adf50079c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42135 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 T#_|TS00000NkvXXu0mjfE)IFE From 00095288a1aef3f42b5f578eea83116ca0f15e0c Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Wed, 14 Apr 2021 18:48:48 +0200 Subject: [PATCH 073/154] Delete x1_thumbnail.png --- resources/profiles/Artillery/x1_thumbnail.png | Bin 36381 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 resources/profiles/Artillery/x1_thumbnail.png diff --git a/resources/profiles/Artillery/x1_thumbnail.png b/resources/profiles/Artillery/x1_thumbnail.png deleted file mode 100644 index 4aa3a0dcccebc7aaa852a8a82eabd9940a9915e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36381 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%0D`01xkydjCR>yIBl{-U7p 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 BwlDwy From 1ed9795340325345e965a2d83cab9005dd61b0eb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 15 Apr 2021 08:41:20 +0200 Subject: [PATCH 074/154] Follow-up of 526233ca472da33070a0d576fe8144100e02cb43 -> Take in account original instances scale factor --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a3d30d5f8d4..50adb89e418 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2425,7 +2425,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode } else if (max_ratio > 5) { const Vec3d inverse = 1.0 / max_ratio * Vec3d::Ones(); - instance->set_scaling_factor(inverse); + instance->set_scaling_factor(inverse.cwiseProduct(instance->get_scaling_factor())); scaled_down = true; } } From 30b6aac535cbf9992564cd681f9c31075a89ac89 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Thu, 15 Apr 2021 10:42:55 +0200 Subject: [PATCH 075/154] Updated MK2 and MK3 bed textures. --- resources/profiles/PrusaResearch/mk2.svg | 886 ++++++++++++++++++++++- resources/profiles/PrusaResearch/mk3.svg | 884 +++++++++++++++++++++- 2 files changed, 1768 insertions(+), 2 deletions(-) diff --git a/resources/profiles/PrusaResearch/mk2.svg b/resources/profiles/PrusaResearch/mk2.svg index b8fa8d0cd71..6214a555239 100644 --- a/resources/profiles/PrusaResearch/mk2.svg +++ b/resources/profiles/PrusaResearch/mk2.svg @@ -1 +1,885 @@ -MK2_bottom \ No newline at end of file + + + + + + image/svg+xml + + MK2_bottom + + + + + MK2_bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/profiles/PrusaResearch/mk3.svg b/resources/profiles/PrusaResearch/mk3.svg index c8f53373b58..03b24d572b5 100644 --- a/resources/profiles/PrusaResearch/mk3.svg +++ b/resources/profiles/PrusaResearch/mk3.svg @@ -1 +1,883 @@ -MK3_bottom \ No newline at end of file + + + + + + image/svg+xml + + MK3_bottom + + + + + MK3_bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e658823f57d7ca6fc7bd6aee2e66f43b629899d4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 12 Jan 2021 17:01:14 +0100 Subject: [PATCH 076/154] Fix for arrange crash when geometry has zero length segments fixes #5749 --- src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index 29a1ccd047c..6b3ff60c18d 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -220,7 +220,9 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, auto next = std::next(first); while(next != sl::cend(sh)) { - edgelist.emplace_back(*(first), *(next)); + if (pl::magnsq(*next - *first) > 0) + edgelist.emplace_back(*(first), *(next)); + ++first; ++next; } } @@ -230,7 +232,9 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, auto next = std::next(first); while(next != sl::cend(other)) { - edgelist.emplace_back(*(next), *(first)); + if (pl::magnsq(*next - *first) > 0) + edgelist.emplace_back(*(next), *(first)); + ++first; ++next; } } From d12bfef0a80a7cdad182ebb2e2b0653fe5e8ab88 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 15 Apr 2021 13:48:20 +0200 Subject: [PATCH 077/154] RemovableManager on OSX: Testing for dictionary values for nullness. Hopefully it fixes Can not start slicer on mac Bigsur #5719 --- src/slic3r/GUI/RemovableDriveManagerMM.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/RemovableDriveManagerMM.mm b/src/slic3r/GUI/RemovableDriveManagerMM.mm index 3bd1fba7eb7..8542c2509da 100644 --- a/src/slic3r/GUI/RemovableDriveManagerMM.mm +++ b/src/slic3r/GUI/RemovableDriveManagerMM.mm @@ -80,7 +80,9 @@ static void unmount_callback(DADiskRef disk, DADissenterRef dissenter, void *con NSLog(@"-%@",(CFStringRef)deviceModelKey); */ if (mediaEjectableKey != nullptr) { - BOOL op = ejectable && (CFEqual(deviceProtocolName, CFSTR("USB")) || CFEqual(deviceModelKey, CFSTR("SD Card Reader")) || CFEqual(deviceProtocolName, CFSTR("Secure Digital"))); + BOOL op = ejectable && + ( (deviceProtocolName != nullptr && (CFEqual(deviceProtocolName, CFSTR("USB")) || CFEqual(deviceProtocolName, CFSTR("Secure Digital")))) || + (deviceModelKey != nullptr && CFEqual(deviceModelKey, CFSTR("SD Card Reader"))) ); //!CFEqual(deviceModelKey, CFSTR("Disk Image")); if (op) [result addObject:volURL.path]; From c052b78005514d26e88dbbaf6d5ac923278d97c5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 15 Apr 2021 16:29:30 +0200 Subject: [PATCH 078/154] After issuing the color change custom G-code, which is most likely just M600, reset the internal retract counter, so that a retract will happen after the firmware returns from M600 to the initial position. Fixes "Blobs on print after manual color change #6362" --- src/libslic3r/GCode.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0e2b1a57f6e..a7994081090 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1778,6 +1778,10 @@ namespace ProcessLayer else { gcode += gcodegen.placeholder_parser_process("color_change_gcode", config.color_change_gcode, current_extruder_id); gcode += "\n"; + //FIXME Tell G-code writer that M600 filled the extruder, thus the G-code writer shall reset the extruder to unretracted state after + // return from M600. Thus the G-code generated by the following line is ignored. + // see GH issue #6362 + gcodegen.writer().unretract(); } } else { From d9f13f14208dc3b1ca503d44e44df63d9d1da17b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 16 Apr 2021 09:48:22 +0200 Subject: [PATCH 079/154] Fixed conversion to utf8 of strings entered using Custom G-code dialog --- src/slic3r/GUI/DoubleSlider.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 29b488b907d..84a499d6fd3 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -2165,7 +2165,7 @@ static std::string get_custom_code(const std::string& code_in, double height) if (dlg.ShowModal() != wxID_OK) return ""; - value = dlg.GetValue().ToStdString(); + value = into_u8(dlg.GetValue()); valid = GUI::Tab::validate_custom_gcode("Custom G-code", value); } while (!valid); return value; @@ -2173,7 +2173,7 @@ static std::string get_custom_code(const std::string& code_in, double height) if (dlg.ShowModal() != wxID_OK) return ""; - return dlg.GetValue().ToStdString(); + return into_u8(dlg.GetValue()); #endif // ENABLE_VALIDATE_CUSTOM_GCODE } From cb98885c2bb0d15edc73c627b0ea0ba8dbbd4f89 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 16 Apr 2021 12:49:57 +0200 Subject: [PATCH 080/154] Fixed flickering of 3D scene GUI when the scene's bounding box gets very big --- src/slic3r/GUI/Camera.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index ade1d404370..6c183c34300 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -330,28 +330,28 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo far_z += FrustrumZMargin; // ensure min size - if (far_z - near_z < FrustrumMinZRange) - { + if (far_z - near_z < FrustrumMinZRange) { double mid_z = 0.5 * (near_z + far_z); double half_size = 0.5 * FrustrumMinZRange; near_z = mid_z - half_size; far_z = mid_z + half_size; } - if (near_z < FrustrumMinNearZ) - { - float delta = FrustrumMinNearZ - near_z; + if (near_z < FrustrumMinNearZ) { + double delta = FrustrumMinNearZ - near_z; set_distance(m_distance + delta); near_z += delta; far_z += delta; } - else if ((near_z > 2.0 * FrustrumMinNearZ) && (m_distance > DefaultDistance)) - { - float delta = m_distance - DefaultDistance; - set_distance(DefaultDistance); - near_z -= delta; - far_z -= delta; - } +// The following is commented out because it causes flickering of the 3D scene GUI +// when the bounding box of the scene gets large enough +// We need to introduce some smarter code to move the camera back and forth in such case +// else if (near_z > 2.0 * FrustrumMinNearZ && m_distance > DefaultDistance) { +// float delta = m_distance - DefaultDistance; +// set_distance(DefaultDistance); +// near_z -= delta; +// far_z -= delta; +// } return ret; } From 61e1b2a993757f17d069d289cd5d87b6653a827e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 16 Apr 2021 13:44:01 +0200 Subject: [PATCH 081/154] Removed mutable members from struct Camera --- src/slic3r/GUI/Camera.cpp | 33 ++++++++++++++++----------------- src/slic3r/GUI/Camera.hpp | 17 ++++++++--------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 6c183c34300..82522b7a337 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -123,7 +123,7 @@ double Camera::get_fov() const void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const { glsafe(::glViewport(0, 0, w, h)); - glsafe(::glGetIntegerv(GL_VIEWPORT, m_viewport.data())); + glsafe(::glGetIntegerv(GL_VIEWPORT, const_cast*>(&m_viewport)->data())); } void Camera::apply_view_matrix() const @@ -139,16 +139,17 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa double h = 0.0; double old_distance = m_distance; - m_frustrum_zs = calc_tight_frustrum_zs_around(box); + std::pair* frustrum_zs = const_cast*>(&m_frustrum_zs); + *frustrum_zs = calc_tight_frustrum_zs_around(box); if (m_distance != old_distance) // the camera has been moved re-apply view matrix apply_view_matrix(); if (near_z > 0.0) - m_frustrum_zs.first = std::max(std::min(m_frustrum_zs.first, near_z), FrustrumMinNearZ); + frustrum_zs->first = std::max(std::min(frustrum_zs->first, near_z), FrustrumMinNearZ); if (far_z > 0.0) - m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z); + frustrum_zs->second = std::max(frustrum_zs->second, far_z); w = 0.5 * (double)m_viewport[2]; h = 0.5 * (double)m_viewport[3]; @@ -162,16 +163,16 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa default: case Ortho: { - m_gui_scale = 1.0; + *const_cast(&m_gui_scale) = 1.0; break; } case Perspective: { // scale near plane to keep w and h constant on the plane at z = m_distance - double scale = m_frustrum_zs.first / m_distance; + double scale = frustrum_zs->first / m_distance; w *= scale; h *= scale; - m_gui_scale = scale; + *const_cast(&m_gui_scale) = scale; break; } } @@ -184,17 +185,17 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa default: case Ortho: { - glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + glsafe(::glOrtho(-w, w, -h, h, frustrum_zs->first, frustrum_zs->second)); break; } case Perspective: { - glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + glsafe(::glFrustum(-w, w, -h, h, frustrum_zs->first, frustrum_zs->second)); break; } } - glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data())); + glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, const_cast(&m_projection_matrix)->data())); glsafe(::glMatrixMode(GL_MODELVIEW)); } @@ -202,8 +203,7 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor) { // Calculate the zoom factor needed to adjust the view around the given box. double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor); - if (zoom > 0.0) - { + if (zoom > 0.0) { m_zoom = zoom; // center view around box center set_target(box.center()); @@ -470,7 +470,7 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c double dx = margin_factor * (max_x - min_x); double dy = margin_factor * (max_y - min_y); - if ((dx <= 0.0) || (dy <= 0.0)) + if (dx <= 0.0 || dy <= 0.0) return -1.0f; return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy); @@ -478,10 +478,9 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c void Camera::set_distance(double distance) const { - if (m_distance != distance) - { - m_view_matrix.translate((distance - m_distance) * get_dir_forward()); - m_distance = distance; + if (m_distance != distance) { + const_cast(&m_view_matrix)->translate((distance - m_distance) * get_dir_forward()); + *const_cast(&m_distance) = distance; } } diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 91f4661b466..574a6eed762 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -35,15 +35,15 @@ private: float m_zenit{ 45.0f }; double m_zoom{ 1.0 }; // Distance between camera position and camera target measured along the camera Z axis - mutable double m_distance{ DefaultDistance }; - mutable double m_gui_scale{ 1.0 }; + double m_distance{ DefaultDistance }; + double m_gui_scale{ 1.0 }; - mutable std::array m_viewport; - mutable Transform3d m_view_matrix{ Transform3d::Identity() }; + std::array m_viewport; + Transform3d m_view_matrix{ Transform3d::Identity() }; // We are calculating the rotation part of the m_view_matrix from m_view_rotation. - mutable Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 }; - mutable Transform3d m_projection_matrix{ Transform3d::Identity() }; - mutable std::pair m_frustrum_zs; + Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 }; + Transform3d m_projection_matrix{ Transform3d::Identity() }; + std::pair m_frustrum_zs; BoundingBoxf3 m_scene_box; @@ -119,8 +119,7 @@ public: bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; } // forces camera right vector to be parallel to XY plane - void recover_from_free_camera() - { + void recover_from_free_camera() { if (std::abs(get_dir_right()(2)) > EPSILON) look_at(get_position(), m_target, Vec3d::UnitZ()); } From 714b70f8d3376a370defa6d6e5acadcdad09f269 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 16 Apr 2021 14:05:55 +0200 Subject: [PATCH 082/154] Further refactoring into struct Camera --- src/slic3r/GUI/Camera.cpp | 156 ++++++++++++++++---------------------- src/slic3r/GUI/Camera.hpp | 10 +-- 2 files changed, 69 insertions(+), 97 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 82522b7a337..d8b80f1ce81 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -21,12 +21,6 @@ double Camera::FrustrumMinNearZ = 100.0; double Camera::FrustrumZMargin = 10.0; double Camera::MaxFovDeg = 60.0; -Camera::Camera() - : requires_zoom_to_bed(false) -{ - set_default_orientation(); -} - std::string Camera::get_type_as_string() const { switch (m_type) @@ -49,11 +43,6 @@ void Camera::set_type(EType type) } } -void Camera::set_type(const std::string& type) -{ - set_type((type == "1") ? Perspective : Ortho); -} - void Camera::select_next_type() { unsigned char next = (unsigned char)m_type + 1; @@ -65,24 +54,18 @@ void Camera::select_next_type() void Camera::set_target(const Vec3d& target) { - Vec3d new_target = validate_target(target); - Vec3d new_displacement = new_target - m_target; - if (!new_displacement.isApprox(Vec3d::Zero())) - { + const Vec3d new_target = validate_target(target); + const Vec3d new_displacement = new_target - m_target; + if (!new_displacement.isApprox(Vec3d::Zero())) { m_target = new_target; m_view_matrix.translate(-new_displacement); } } -void Camera::update_zoom(double delta_zoom) -{ - set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * 0.1)); -} - void Camera::set_zoom(double zoom) { // Don't allow to zoom too far outside the scene. - double zoom_min = min_zoom(); + const double zoom_min = min_zoom(); if (zoom_min > 0.0) zoom = std::max(zoom, zoom_min); @@ -138,7 +121,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa double w = 0.0; double h = 0.0; - double old_distance = m_distance; + const double old_distance = m_distance; std::pair* frustrum_zs = const_cast*>(&m_frustrum_zs); *frustrum_zs = calc_tight_frustrum_zs_around(box); if (m_distance != old_distance) @@ -154,7 +137,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa w = 0.5 * (double)m_viewport[2]; h = 0.5 * (double)m_viewport[3]; - double inv_zoom = get_inv_zoom(); + const double inv_zoom = get_inv_zoom(); w *= inv_zoom; h *= inv_zoom; @@ -169,7 +152,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa case Perspective: { // scale near plane to keep w and h constant on the plane at z = m_distance - double scale = frustrum_zs->first / m_distance; + const double scale = frustrum_zs->first / m_distance; w *= scale; h *= scale; *const_cast(&m_gui_scale) = scale; @@ -202,7 +185,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor) { // Calculate the zoom factor needed to adjust the view around the given box. - double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor); + const double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor); if (zoom > 0.0) { m_zoom = zoom; // center view around box center @@ -213,9 +196,8 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor) void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor) { Vec3d center; - double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor); - if (zoom > 0.0) - { + const double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor); + if (zoom > 0.0) { m_zoom = zoom; // center view around the calculated center set_target(center); @@ -289,8 +271,8 @@ void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, b } } - Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; - auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ()); + const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; + const auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ()); m_view_rotation *= rot_z * Eigen::AngleAxisd(delta_zenit_rad, rot_z.inverse() * get_dir_right()); m_view_rotation.normalize(); m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.)); @@ -299,10 +281,10 @@ void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, b // Virtual trackball, rotate around an axis, where the eucledian norm of the axis gives the rotation angle in radians. void Camera::rotate_local_around_target(const Vec3d& rotation_rad) { - double angle = rotation_rad.norm(); + const double angle = rotation_rad.norm(); if (std::abs(angle) > EPSILON) { - Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; - Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized(); + const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; + const Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized(); m_view_rotation *= Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)); m_view_rotation.normalize(); m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.)); @@ -310,18 +292,13 @@ void Camera::rotate_local_around_target(const Vec3d& rotation_rad) } } -double Camera::min_zoom() const -{ - return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); -} - std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const { std::pair ret; auto& [near_z, far_z] = ret; // box in eye space - BoundingBoxf3 eye_box = box.transformed(m_view_matrix); + const BoundingBoxf3 eye_box = box.transformed(m_view_matrix); near_z = -eye_box.max(2); far_z = -eye_box.min(2); @@ -331,14 +308,14 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo // ensure min size if (far_z - near_z < FrustrumMinZRange) { - double mid_z = 0.5 * (near_z + far_z); - double half_size = 0.5 * FrustrumMinZRange; + const double mid_z = 0.5 * (near_z + far_z); + const double half_size = 0.5 * FrustrumMinZRange; near_z = mid_z - half_size; far_z = mid_z + half_size; } if (near_z < FrustrumMinNearZ) { - double delta = FrustrumMinNearZ - near_z; + const double delta = FrustrumMinNearZ - near_z; set_distance(m_distance + delta); near_z += delta; far_z += delta; @@ -358,45 +335,43 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor) const { - double max_bb_size = box.max_size(); + const double max_bb_size = box.max_size(); if (max_bb_size == 0.0) return -1.0; // project the box vertices on a plane perpendicular to the camera forward axis // then calculates the vertices coordinate on this plane along the camera xy axes - Vec3d right = get_dir_right(); - Vec3d up = get_dir_up(); - Vec3d forward = get_dir_forward(); - - Vec3d bb_center = box.center(); + const Vec3d right = get_dir_right(); + const Vec3d up = get_dir_up(); + const Vec3d forward = get_dir_forward(); + const Vec3d bb_center = box.center(); // box vertices in world space - std::vector vertices; - vertices.reserve(8); - vertices.push_back(box.min); - vertices.emplace_back(box.max(0), box.min(1), box.min(2)); - vertices.emplace_back(box.max(0), box.max(1), box.min(2)); - vertices.emplace_back(box.min(0), box.max(1), box.min(2)); - vertices.emplace_back(box.min(0), box.min(1), box.max(2)); - vertices.emplace_back(box.max(0), box.min(1), box.max(2)); - vertices.push_back(box.max); - vertices.emplace_back(box.min(0), box.max(1), box.max(2)); + const std::vector vertices = { + box.min, + { box.max(0), box.min(1), box.min(2) }, + { box.max(0), box.max(1), box.min(2) }, + { box.min(0), box.max(1), box.min(2) }, + { box.min(0), box.min(1), box.max(2) }, + { box.max(0), box.min(1), box.max(2) }, + box.max, + { box.min(0), box.max(1), box.max(2) } + }; double min_x = DBL_MAX; double min_y = DBL_MAX; double max_x = -DBL_MAX; double max_y = -DBL_MAX; - for (const Vec3d& v : vertices) - { + for (const Vec3d& v : vertices) { // project vertex on the plane perpendicular to camera forward axis - Vec3d pos = v - bb_center; - Vec3d proj_on_plane = pos - pos.dot(forward) * forward; + const Vec3d pos = v - bb_center; + const Vec3d proj_on_plane = pos - pos.dot(forward) * forward; // calculates vertex coordinate along camera xy axes - double x_on_plane = proj_on_plane.dot(right); - double y_on_plane = proj_on_plane.dot(up); + const double x_on_plane = proj_on_plane.dot(right); + const double y_on_plane = proj_on_plane.dot(up); min_x = std::min(min_x, x_on_plane); min_y = std::min(min_y, y_on_plane); @@ -406,7 +381,7 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double double dx = max_x - min_x; double dy = max_y - min_y; - if ((dx <= 0.0) || (dy <= 0.0)) + if (dx <= 0.0 || dy <= 0.0) return -1.0f; dx *= margin_factor; @@ -423,13 +398,12 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c // project the volumes vertices on a plane perpendicular to the camera forward axis // then calculates the vertices coordinate on this plane along the camera xy axes - Vec3d right = get_dir_right(); - Vec3d up = get_dir_up(); - Vec3d forward = get_dir_forward(); + const Vec3d right = get_dir_right(); + const Vec3d up = get_dir_up(); + const Vec3d forward = get_dir_forward(); BoundingBoxf3 box; - for (const GLVolume* volume : volumes) - { + for (const GLVolume* volume : volumes) { box.merge(volume->transformed_bounding_box()); } center = box.center(); @@ -439,24 +413,22 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c double max_x = -DBL_MAX; double max_y = -DBL_MAX; - for (const GLVolume* volume : volumes) - { + for (const GLVolume* volume : volumes) { const Transform3d& transform = volume->world_matrix(); const TriangleMesh* hull = volume->convex_hull(); if (hull == nullptr) continue; - for (const Vec3f& vertex : hull->its.vertices) - { - Vec3d v = transform * vertex.cast(); + for (const Vec3f& vertex : hull->its.vertices) { + const Vec3d v = transform * vertex.cast(); // project vertex on the plane perpendicular to camera forward axis - Vec3d pos = v - center; - Vec3d proj_on_plane = pos - pos.dot(forward) * forward; + const Vec3d pos = v - center; + const Vec3d proj_on_plane = pos - pos.dot(forward) * forward; // calculates vertex coordinate along camera xy axes - double x_on_plane = proj_on_plane.dot(right); - double y_on_plane = proj_on_plane.dot(up); + const double x_on_plane = proj_on_plane.dot(right); + const double y_on_plane = proj_on_plane.dot(up); min_x = std::min(min_x, x_on_plane); min_y = std::min(min_y, y_on_plane); @@ -467,8 +439,8 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c center += 0.5 * (max_x + min_x) * right + 0.5 * (max_y + min_y) * up; - double dx = margin_factor * (max_x - min_x); - double dy = margin_factor * (max_y - min_y); + const double dx = margin_factor * (max_x - min_x); + const double dy = margin_factor * (max_y - min_y); if (dx <= 0.0 || dy <= 0.0) return -1.0f; @@ -486,13 +458,13 @@ void Camera::set_distance(double distance) const void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) { - Vec3d unit_z = (position - target).normalized(); - Vec3d unit_x = up.cross(unit_z).normalized(); - Vec3d unit_y = unit_z.cross(unit_x).normalized(); + const Vec3d unit_z = (position - target).normalized(); + const Vec3d unit_x = up.cross(unit_z).normalized(); + const Vec3d unit_y = unit_z.cross(unit_x).normalized(); m_target = target; m_distance = (position - target).norm(); - Vec3d new_position = m_target + m_distance * unit_z; + const Vec3d new_position = m_target + m_distance * unit_z; m_view_matrix(0, 0) = unit_x(0); m_view_matrix(0, 1) = unit_x(1); @@ -524,10 +496,10 @@ void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up void Camera::set_default_orientation() { m_zenit = 45.0f; - double theta_rad = Geometry::deg2rad(-(double)m_zenit); - double phi_rad = Geometry::deg2rad(45.0); - double sin_theta = ::sin(theta_rad); - Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); + const double theta_rad = Geometry::deg2rad(-(double)m_zenit); + const double phi_rad = Geometry::deg2rad(45.0); + const double sin_theta = ::sin(theta_rad); + const Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); m_view_rotation = Eigen::AngleAxisd(theta_rad, Vec3d::UnitX()) * Eigen::AngleAxisd(phi_rad, Vec3d::UnitZ()); m_view_rotation.normalize(); m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- camera_pos), m_view_rotation, Vec3d(1., 1., 1.)); @@ -542,9 +514,9 @@ Vec3d Camera::validate_target(const Vec3d& target) const test_box.scale(ScaleFactor); test_box.translate(m_scene_box.center()); - return Vec3d(std::clamp(target(0), test_box.min(0), test_box.max(0)), - std::clamp(target(1), test_box.min(1), test_box.max(1)), - std::clamp(target(2), test_box.min(2), test_box.max(2))); + return { std::clamp(target(0), test_box.min(0), test_box.max(0)), + std::clamp(target(1), test_box.min(1), test_box.max(1)), + std::clamp(target(2), test_box.min(2), test_box.max(2)) }; } void Camera::update_zenit() diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 574a6eed762..1abb430ca96 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -26,7 +26,7 @@ struct Camera Num_types }; - bool requires_zoom_to_bed; + bool requires_zoom_to_bed{ false }; private: EType m_type{ Perspective }; @@ -48,13 +48,13 @@ private: BoundingBoxf3 m_scene_box; public: - Camera(); + Camera() { set_default_orientation(); } EType get_type() const { return m_type; } std::string get_type_as_string() const; void set_type(EType type); // valid values for type: "0" -> ortho, "1" -> perspective - void set_type(const std::string& type); + void set_type(const std::string& type) { set_type((type == "1") ? Perspective : Ortho); } void select_next_type(); void enable_update_config_on_type_change(bool enable) { m_update_config_on_type_change_enabled = enable; } @@ -67,7 +67,7 @@ public: double get_zoom() const { return m_zoom; } double get_inv_zoom() const { assert(m_zoom != 0.0); return 1.0 / m_zoom; } - void update_zoom(double delta_zoom); + void update_zoom(double delta_zoom) { set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * 0.1)); } void set_zoom(double zoom); const BoundingBoxf3& get_scene_box() const { return m_scene_box; } @@ -127,7 +127,7 @@ public: void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up); double max_zoom() const { return 250.0; } - double min_zoom() const; + double min_zoom() const { return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); } private: // returns tight values for nearZ and farZ plane around the given bounding box From 24eed24037164eda6be9261b01e6595e62a6ca27 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 16 Apr 2021 15:25:03 +0200 Subject: [PATCH 083/154] Removed mutable members from class Selection --- src/slic3r/GUI/Selection.cpp | 88 ++++++++++++++++-------------------- src/slic3r/GUI/Selection.hpp | 14 +++--- 2 files changed, 45 insertions(+), 57 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index faf25ff8bc6..2acb8cb85b1 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1102,39 +1102,32 @@ void Selection::erase() if (is_single_full_object()) wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itObject, get_object_idx(), 0); - else if (is_multiple_full_object()) - { + else if (is_multiple_full_object()) { std::vector items; items.reserve(m_cache.content.size()); - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) - { + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) { items.emplace_back(ItemType::itObject, it->first, 0); } wxGetApp().obj_list()->delete_from_model_and_list(items); } - else if (is_multiple_full_instance()) - { + else if (is_multiple_full_instance()) { std::set> instances_idxs; - for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) - { - for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) - { + for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) { + for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) { instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); } } std::vector items; items.reserve(instances_idxs.size()); - for (const std::pair& i : instances_idxs) - { + for (const std::pair& i : instances_idxs) { items.emplace_back(ItemType::itInstance, i.first, i.second); } wxGetApp().obj_list()->delete_from_model_and_list(items); } else if (is_single_full_instance()) wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itInstance, get_object_idx(), get_instance_idx()); - else if (is_mixed()) - { + else if (is_mixed()) { std::set items_set; std::map volumes_in_obj; @@ -1186,11 +1179,9 @@ void Selection::erase() wxGetApp().obj_list()->delete_from_model_and_list(items); } - else - { + else { std::set> volumes_idxs; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* v = (*m_volumes)[i]; // Only remove volumes associated with ModelVolumes from the object list. // Temporary meshes (SLA supports or pads) are not managed by the object list. @@ -1200,8 +1191,7 @@ void Selection::erase() std::vector items; items.reserve(volumes_idxs.size()); - for (const std::pair& v : volumes_idxs) - { + for (const std::pair& v : volumes_idxs) { items.emplace_back(ItemType::itVolume, v.first, v.second); } @@ -1214,7 +1204,7 @@ void Selection::render(float scale_factor) const if (!m_valid || is_empty()) return; - m_scale_factor = scale_factor; + *const_cast(&m_scale_factor) = scale_factor; // render cumulative bounding box of selected volumes render_selected_volumes(); @@ -1224,7 +1214,7 @@ void Selection::render(float scale_factor) const #if ENABLE_RENDER_SELECTION_CENTER void Selection::render_center(bool gizmo_is_dragging) const { - if (!m_valid || is_empty() || (m_quadric == nullptr)) + if (!m_valid || is_empty() || m_quadric == nullptr) return; Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); @@ -1250,8 +1240,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const GLShaderProgram* shader = nullptr; - if (!boost::starts_with(sidebar_field, "layer")) - { + if (!boost::starts_with(sidebar_field, "layer")) { shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; @@ -1735,18 +1724,16 @@ void Selection::do_remove_volume(unsigned int volume_idx) void Selection::do_remove_instance(unsigned int object_idx, unsigned int instance_idx) { - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx)) + if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) do_remove_volume(i); } } void Selection::do_remove_object(unsigned int object_idx) { - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { GLVolume* v = (*m_volumes)[i]; if (v->object_idx() == (int)object_idx) do_remove_volume(i); @@ -1755,47 +1742,48 @@ void Selection::do_remove_object(unsigned int object_idx) void Selection::calc_bounding_box() const { - m_bounding_box = BoundingBoxf3(); - if (m_valid) - { - for (unsigned int i : m_list) - { - m_bounding_box.merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); + BoundingBoxf3* bounding_box = const_cast(&m_bounding_box); + *bounding_box = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + bounding_box->merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); } } - m_bounding_box_dirty = false; + *const_cast(&m_bounding_box_dirty) = false; } void Selection::calc_unscaled_instance_bounding_box() const { - m_unscaled_instance_bounding_box = BoundingBoxf3(); - if (m_valid) { - for (unsigned int i : m_list) { - const GLVolume &volume = *(*m_volumes)[i]; + BoundingBoxf3* unscaled_instance_bounding_box = const_cast(&m_unscaled_instance_bounding_box); + *unscaled_instance_bounding_box = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; - Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); - trafo.translation()(2) += volume.get_sla_shift_z(); - m_unscaled_instance_bounding_box.merge(volume.transformed_convex_hull_bounding_box(trafo)); - } - } - m_unscaled_instance_bounding_box_dirty = false; + Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); + trafo.translation()(2) += volume.get_sla_shift_z(); + unscaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + *const_cast(&m_unscaled_instance_bounding_box_dirty) = false; } void Selection::calc_scaled_instance_bounding_box() const { - m_scaled_instance_bounding_box = BoundingBoxf3(); + BoundingBoxf3* scaled_instance_bounding_box = const_cast(&m_scaled_instance_bounding_box); + *scaled_instance_bounding_box = BoundingBoxf3(); if (m_valid) { for (unsigned int i : m_list) { - const GLVolume &volume = *(*m_volumes)[i]; + const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); trafo.translation()(2) += volume.get_sla_shift_z(); - m_scaled_instance_bounding_box.merge(volume.transformed_convex_hull_bounding_box(trafo)); + scaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo)); } } - m_scaled_instance_bounding_box_dirty = false; + *const_cast(&m_scaled_instance_bounding_box_dirty) = false; } void Selection::render_selected_volumes() const diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index a9095adcbff..8bb418baac6 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -206,14 +206,14 @@ private: IndicesList m_list; Cache m_cache; Clipboard m_clipboard; - mutable BoundingBoxf3 m_bounding_box; - mutable bool m_bounding_box_dirty; + BoundingBoxf3 m_bounding_box; + bool m_bounding_box_dirty; // Bounding box of a selection, with no instance scaling applied. This bounding box // is useful for absolute scaling of tilted objects in world coordinate space. - mutable BoundingBoxf3 m_unscaled_instance_bounding_box; - mutable bool m_unscaled_instance_bounding_box_dirty; - mutable BoundingBoxf3 m_scaled_instance_bounding_box; - mutable bool m_scaled_instance_bounding_box_dirty; + BoundingBoxf3 m_unscaled_instance_bounding_box; + bool m_unscaled_instance_bounding_box_dirty; + BoundingBoxf3 m_scaled_instance_bounding_box; + bool m_scaled_instance_bounding_box_dirty; #if ENABLE_RENDER_SELECTION_CENTER GLUquadricObj* m_quadric; @@ -222,7 +222,7 @@ private: GLModel m_arrow; GLModel m_curved_arrow; - mutable float m_scale_factor; + float m_scale_factor; public: Selection(); From e5b7c3536ac53bc3c98a2507ffe27fb4610252da Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 16 Apr 2021 15:49:37 +0200 Subject: [PATCH 084/154] Removed mutable members from class GLToolbar --- src/slic3r/GUI/GLToolbar.cpp | 90 ++++++++++++++---------------------- src/slic3r/GUI/GLToolbar.hpp | 6 +-- 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 79e7ea1c647..0e6a4ce2918 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -428,8 +428,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) bool processed = false; // mouse anywhere - if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr)) - { + if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && m_mouse_capture.parent != nullptr) { if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())) { // prevents loosing selection into the scene if mouse down was done inside the toolbar and mouse up was down outside it, // as when switching between views @@ -441,38 +440,31 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) if (evt.Moving()) update_hover_state(mouse_pos, parent); - else if (evt.LeftUp()) - { - if (m_mouse_capture.left) - { + else if (evt.LeftUp()) { + if (m_mouse_capture.left) { processed = true; m_mouse_capture.left = false; } else return false; } - else if (evt.MiddleUp()) - { - if (m_mouse_capture.middle) - { + else if (evt.MiddleUp()) { + if (m_mouse_capture.middle) { processed = true; m_mouse_capture.middle = false; } else return false; } - else if (evt.RightUp()) - { - if (m_mouse_capture.right) - { + else if (evt.RightUp()) { + if (m_mouse_capture.right) { processed = true; m_mouse_capture.right = false; } else return false; } - else if (evt.Dragging()) - { + else if (evt.Dragging()) { if (m_mouse_capture.any()) // if the button down was done on this toolbar, prevent from dragging into the scene processed = true; @@ -481,35 +473,29 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) } int item_id = contains_mouse(mouse_pos, parent); - if (item_id != -1) - { + if (item_id != -1) { // mouse inside toolbar - if (evt.LeftDown() || evt.LeftDClick()) - { + if (evt.LeftDown() || evt.LeftDClick()) { m_mouse_capture.left = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && - ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left))) - { + if (item_id != -2 && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && + (m_pressed_toggable_id == -1 || m_items[item_id]->get_last_action_type() == GLToolbarItem::Left)) { // mouse is inside an icon do_action(GLToolbarItem::Left, item_id, parent, true); parent.set_as_dirty(); } } - else if (evt.MiddleDown()) - { + else if (evt.MiddleDown()) { m_mouse_capture.middle = true; m_mouse_capture.parent = &parent; } - else if (evt.RightDown()) - { + else if (evt.RightDown()) { m_mouse_capture.right = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && - ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right))) - { + if (item_id != -2 && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && + (m_pressed_toggable_id == -1 || m_items[item_id]->get_last_action_type() == GLToolbarItem::Right)) { // mouse is inside an icon do_action(GLToolbarItem::Right, item_id, parent, true); parent.set_as_dirty(); @@ -522,24 +508,26 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) void GLToolbar::calc_layout() const { - switch (m_layout.type) + Layout* layout = const_cast(&m_layout); + + switch (layout->type) { default: case Layout::Horizontal: { - m_layout.width = get_width_horizontal(); - m_layout.height = get_height_horizontal(); + layout->width = get_width_horizontal(); + layout->height = get_height_horizontal(); break; } case Layout::Vertical: { - m_layout.width = get_width_vertical(); - m_layout.height = get_height_vertical(); + layout->width = get_width_vertical(); + layout->height = get_height_vertical(); break; } } - m_layout.dirty = false; + layout->dirty = false; } float GLToolbar::get_width_horizontal() const @@ -1196,19 +1184,17 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const left += scaled_border; top -= scaled_border; - if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) + if (tex_id == 0 || tex_width <= 0 || tex_height <= 0) return; // renders icons - for (const GLToolbarItem* item : m_items) - { + for (const GLToolbarItem* item : m_items) { if (!item->is_visible()) continue; if (item->is_separator()) top -= separator_stride; - else - { + else { item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); top -= icon_stride; } @@ -1219,16 +1205,14 @@ bool GLToolbar::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; std::vector filenames; - for (GLToolbarItem* item : m_items) - { + for (GLToolbarItem* item : m_items) { const std::string& icon_filename = item->get_icon_filename(); if (!icon_filename.empty()) filenames.push_back(path + icon_filename); } std::vector> states; - if (m_type == Normal) - { + if (m_type == Normal) { states.push_back({ 1, false }); // Normal states.push_back({ 0, false }); // Pressed states.push_back({ 2, false }); // Disabled @@ -1236,8 +1220,7 @@ bool GLToolbar::generate_icons_texture() const states.push_back({ 0, false }); // HoverPressed states.push_back({ 2, false }); // HoverDisabled } - else - { + else { states.push_back({ 1, false }); // Normal states.push_back({ 1, true }); // Pressed states.push_back({ 1, false }); // Disabled @@ -1251,9 +1234,9 @@ bool GLToolbar::generate_icons_texture() const // if (sprite_size_px % 2 != 0) // sprite_size_px += 1; - bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false); + bool res = const_cast(&m_icons_texture)->load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false); if (res) - m_icons_texture_dirty = false; + *const_cast(&m_icons_texture_dirty) = false; return res; } @@ -1262,8 +1245,7 @@ bool GLToolbar::update_items_visibility() { bool ret = false; - for (GLToolbarItem* item : m_items) - { + for (GLToolbarItem* item : m_items) { ret |= item->update_visibility(); } @@ -1272,12 +1254,10 @@ bool GLToolbar::update_items_visibility() // updates separators visibility to avoid having two of them consecutive bool any_item_visible = false; - for (GLToolbarItem* item : m_items) - { + for (GLToolbarItem* item : m_items) { if (!item->is_separator()) any_item_visible |= item->is_visible(); - else - { + else { item->set_visible(any_item_visible); any_item_visible = false; } diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 74e18de975e..3237c44953e 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -233,10 +233,10 @@ private: EType m_type; std::string m_name; bool m_enabled; - mutable GLTexture m_icons_texture; - mutable bool m_icons_texture_dirty; + GLTexture m_icons_texture; + bool m_icons_texture_dirty; BackgroundTexture m_background_texture; - mutable Layout m_layout; + Layout m_layout; ItemsList m_items; From cf2bcd5c6652b82247f11a00c7b65d9b8ab79245 Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Fri, 16 Apr 2021 19:30:33 +0200 Subject: [PATCH 085/154] creality.ini: more accurate spool weights for Devil Design I just noticed Devil Design uses identical spools to Extrudr NX-2 --- resources/profiles/Creality.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 80c76932d07..9771e7ac6c3 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -637,7 +637,7 @@ first_layer_bed_temperature = 60 filament_cost = 19.00 filament_density = 1.24 filament_colour = #FF0000 -filament_spool_weight = 250 +filament_spool_weight = 256 [filament:Devil Design PLA (Galaxy) @CREALITY] inherits = *PLA* @@ -649,7 +649,7 @@ first_layer_bed_temperature = 65 filament_cost = 19.00 filament_density = 1.24 filament_colour = #FF0000 -filament_spool_weight = 250 +filament_spool_weight = 256 [filament:Extrudr PLA NX2 @CREALITY] inherits = *PLA* From 8c97ad0ae73730360ddc456ffe8276ad781279ed Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Fri, 16 Apr 2021 19:32:32 +0200 Subject: [PATCH 086/154] creality.ini: remove parentheses for galaxy pla filament since we've not using those for other filament subtypes --- resources/profiles/Creality.ini | 61 +++++++++++++++++---------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 9771e7ac6c3..95ce8a0dce7 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -21,7 +21,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3BLTOUCH] name = Creality Ender-3 BLTouch @@ -30,7 +30,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3V2] name = Creality Ender-3 V2 @@ -39,7 +39,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3MAX] name = Creality Ender-3 Max @@ -48,7 +48,7 @@ technology = FFF family = ENDER bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER4] name = Creality Ender-4 @@ -57,7 +57,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5] name = Creality Ender-5 @@ -66,7 +66,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5PLUS] name = Creality Ender-5 Plus @@ -75,7 +75,7 @@ technology = FFF family = ENDER bed_model = ender5plus_bed.stl bed_texture = ender5plus.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER6] name = Creality Ender-6 @@ -84,7 +84,7 @@ technology = FFF family = ENDER bed_model = ender6_bed.stl bed_texture = ender6.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER2] name = Creality Ender-2 @@ -93,7 +93,7 @@ technology = FFF family = ENDER bed_model = ender2_bed.stl bed_texture = ender2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR5PRO] name = Creality CR-5 Pro @@ -102,7 +102,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR5PROH] name = Creality CR-5 Pro H @@ -111,7 +111,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR6SE] name = Creality CR-6 SE @@ -120,7 +120,7 @@ technology = FFF family = CR bed_model = cr6se_bed.stl bed_texture = cr6se.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR6MAX] name = Creality CR-6 Max @@ -129,7 +129,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MINI] name = Creality CR-10 Mini @@ -138,7 +138,7 @@ technology = FFF family = CR bed_model = cr10mini_bed.stl bed_texture = cr10mini.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MAX] name = Creality CR-10 Max @@ -147,7 +147,7 @@ technology = FFF family = CR bed_model = cr10max_bed.stl bed_texture = cr10max.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10] name = Creality CR-10 @@ -156,7 +156,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V2] name = Creality CR-10 V2 @@ -165,7 +165,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V3] name = Creality CR-10 V3 @@ -174,7 +174,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S] name = Creality CR-10 S @@ -183,7 +183,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPRO] name = Creality CR-10 S Pro @@ -192,7 +192,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPROV2] name = Creality CR-10 S Pro V2 @@ -201,7 +201,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S4] name = Creality CR-10 S4 @@ -210,7 +210,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S5] name = Creality CR-10 S5 @@ -219,7 +219,7 @@ technology = FFF family = CR bed_model = cr10s5_bed.stl bed_texture = cr10s5.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20] name = Creality CR-20 @@ -228,7 +228,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20PRO] name = Creality CR-20 Pro @@ -237,7 +237,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR200B] name = Creality CR-200B @@ -246,7 +246,7 @@ technology = FFF family = CR bed_model = cr200b_bed.stl bed_texture = cr200b.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR8] name = Creality CR-8 @@ -255,7 +255,7 @@ technology = FFF family = CR bed_model = cr8_bed.stl bed_texture = cr8.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRX] #name = Creality CR-X @@ -264,7 +264,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRXPRO] #name = Creality CR-X Pro @@ -273,7 +273,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA (Galaxy) @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -639,8 +639,9 @@ filament_density = 1.24 filament_colour = #FF0000 filament_spool_weight = 256 -[filament:Devil Design PLA (Galaxy) @CREALITY] +[filament:Devil Design PLA Galaxy @CREALITY] inherits = *PLA* +renamed_from = "Devil Design PLA (Galaxy) @CREALITY" filament_vendor = Devil Design temperature = 225 bed_temperature = 65 From 5dbb7c8d571f436a59460882b749d23870f0da85 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 19 Apr 2021 15:48:07 +0200 Subject: [PATCH 087/154] Disabled thick bridges, updated support settings. Bundle refactoring. --- resources/profiles/PrusaResearch.idx | 320 ++-- resources/profiles/PrusaResearch.ini | 2072 ++++++++++---------------- 2 files changed, 964 insertions(+), 1428 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 8c14026ce10..dc3c79cd55c 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,159 +1,161 @@ -min_slic3r_version = 2.3.0-rc1 -1.2.4 Updated cost/density values in filament settings. Various changes in print settings. -1.2.3 Updated firmware version. Updated end g-code in MMU2 printer profiles. -1.2.2 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. -1.2.1 Updated FW version for MK2.5 family printers. -1.2.0 Added full_fan_speed_layer value for PETG. Increased support interface spacing for 0.6mm nozzle profiles. Updated firmware version. -min_slic3r_version = 2.3.0-beta2 -1.2.0-beta1 Updated end g-code. Added full_fan_speed_layer values. -min_slic3r_version = 2.3.0-beta0 -1.2.0-beta0 Adjusted infill anchor limits. Added filament spool weights. -min_slic3r_version = 2.3.0-alpha4 -1.2.0-alpha1 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. -1.2.0-alpha0 Added filament spool weights -min_slic3r_version = 2.2.0-alpha3 -1.1.13 Updated firmware version. Updated end g-code in MMU2 printer profiles. -1.1.12 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. -1.1.11 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. -1.1.10 Updated firmware version. -1.1.9 Updated K values in filament profiles (linear advance). Added new filament profiles and SLA materials. -1.1.8 Updated start/end g-code scripts for MK3 family printer profiles (reduced extruder motor current for some print profiles). Added new filament and SLA material profiles. -1.1.7 Updated end g-code for MMU2 Single printer profiles. Added/updated filament and SLA material profiles. -1.1.6 Updated firmware version for MK2.5/S and MK3/S. -1.1.5 Updated MMU1 specific retraction settings for Prusament PC Blend -1.1.4 Added Prusament PC Blend filament profile. -1.1.3 Added SLA material and filament profile -1.1.2 Added renamed_from fields for PETG filaments to indicate that they were renamed from PET. -1.1.1 Added Verbatim and Fiberlogy PETG filament profiles. Updated auto cooling settings for ABS. -1.1.1-beta Updated for PrusaSlicer 2.2.0-beta -1.1.1-alpha4 Extended list of default filaments to be installed, top/bottom_solid_min_thickness defined, infill_acceleration changed etc -1.1.1-alpha3 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer. -# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer, -# so they will see the print bed. -max_slic3r_version = 2.2.0-alpha2 -min_slic3r_version = 2.2.0-alpha0 -1.1.1-alpha2 Bumped up config version, so our in house customer will get updated profiles. -1.1.0 Filament aliases, Creality profiles and other goodies for PrusaSlicer 2.2.0-alpha0 -min_slic3r_version = 2.1.1-beta0 -1.0.11 Updated firmware version. -1.0.10 Updated firmware version for MK2.5/S and MK3/S. -1.0.9 Updated firmware version for MK2.5/S and MK3/S. -1.0.8 Various changes in FFF profiles, new filaments/materials added. See changelog. -1.0.7 Updated layer height limits for MINI -1.0.6 Added Prusa MINI profiles -min_slic3r_version = 2.1.0-alpha0 -1.0.5 Added SLA materials -1.0.4 Updated firmware version and 0.25mm nozzle profiles -1.0.3 Added filament profiles -1.0.2 Added SLA materials -1.0.1 Updated MK3 firmware version check to 3.8.0, new soluble support profiles for 0.6mm nozzle diameter MMU2S printers. -1.0.0 Updated end G-code for the MMU2 profiles to lift the extruder at the end of print. Wipe tower bridging distance was made smaller for soluble supports. -1.0.0-beta1 Updated color for the ASA filaments to differ from the other filaments. Single extruder printers now have no extruder color assigned, obects and toolpaths will be colored with the color of the active filament. -1.0.0-beta0 Printer model checks in start G-codes, ASA filament profiles, limits on min / max SL1 exposition times -1.0.0-alpha2 Printer model and nozzle diameter check -1.0.0-alpha1 Added Prusament ASA profile -1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX -min_slic3r_version = 1.42.0-alpha6 -0.8.10 Updated firmware version. -0.8.9 Updated firmware version for MK2.5/S and MK3/S. -0.8.8 Updated firmware version for MK2.5/S and MK3/S. -0.8.7 Updated firmware version -0.8.6 Updated firmware version for MK2.5/S and MK3/S -0.8.5 Updated SL1 printer and material settings -0.8.4 Added Prusament ASA profile -0.8.3 FW version and SL1 materials update -0.8.2 FFF and SL1 settings update -0.8.1 Output settings and SLA materials update -0.8.0 Updated for the PrusaSlicer 2.0.0 final release -0.8.0-rc2 Updated firmware versions for MK2.5/S and MK3/S -0.8.0-rc1 Updated SLA profiles -0.8.0-rc Updated for the PrusaSlicer 2.0.0-rc release -0.8.0-beta4 Updated SLA profiles -0.8.0-beta3 Updated SLA profiles -0.8.0-beta2 Updated SLA profiles -0.8.0-beta1 Updated SLA profiles -0.8.0-beta Updated SLA profiles -0.8.0-alpha9 Updated SLA and FFF profiles -0.8.0-alpha8 Updated SLA profiles -0.8.0-alpha7 Updated SLA profiles -0.8.0-alpha6 Updated SLA profiles -min_slic3r_version = 1.42.0-alpha -0.8.0-alpha Updated SLA profiles -0.4.0-alpha4 Updated SLA profiles -0.4.0-alpha3 Update of SLA profiles -0.4.0-alpha2 First SLA profiles -min_slic3r_version = 1.41.3-alpha -0.4.12 Updated firmware version for MK2.5/S and MK3/S. -0.4.11 Updated firmware version for MK2.5/S and MK3/S. -0.4.10 Updated firmware version -0.4.9 Updated firmware version for MK2.5/S and MK3/S -0.4.8 MK2.5/3/S FW update -0.4.7 MK2/S/MMU FW update -0.4.6 Updated firmware versions for MK2.5/S and MK3/S -0.4.5 Enabled remaining time support for MK2/S/MMU1 -0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.4.1 New MK2.5S and MK3S FW versions -0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -min_slic3r_version = 1.41.1 -0.3.11 Updated firmware version for MK2.5/S and MK3/S. -0.3.10 Updated firmware version -0.3.9 Updated firmware version for MK2.5/S and MK3/S -0.3.8 MK2.5/3/S FW update -0.3.7 MK2/S/MMU FW update -0.3.6 Updated firmware versions for MK2.5 and MK3 -0.3.5 New MK2.5 and MK3 FW versions -0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt -0.3.3 Prusament PETG released -0.3.2 New MK2.5 and MK3 FW versions -0.3.1 New MK2.5 and MK3 FW versions -0.3.0 New MK2.5 and MK3 FW version -min_slic3r_version = 1.41.0-alpha -0.2.9 New MK2.5 and MK3 FW versions -0.2.8 New MK2.5 and MK3 FW version -min_slic3r_version = 1.41.1 -0.2.7 New MK2.5 and MK3 FW version -0.2.6 Added MMU2 MK2.5 settings -min_slic3r_version = 1.41.0-alpha -0.2.5 Prusament is out - added prusament settings -0.2.4 Added soluble support profiles for MMU2 -0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit -0.2.2 Edited MMU2 Single mode purge line -0.2.1 Added PET and BVOH settings for MMU2 -0.2.0-beta5 Fixed MMU1 ramming parameters -0.2.0-beta4 Added filament loading speed at start, increased minimal purge on wipe tower -0.2.0-beta3 Edited ramming parameters and filament cooling moves for MMU2 -0.2.0-beta2 Edited first layer speed and wipe tower position -0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles -0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets. -0.2.0-alpha7 Vojtech's fix the incorrect *MK3* references -0.2.0-alpha6 Jindra's way to fix the 0.2.0-alpha5 version -0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 -0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. -0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost -0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material -0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 -0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters -min_slic3r_version = 1.40.0 -0.1.18 Updated firmware version -0.1.17 Updated firmware version for MK2.5/S and MK3/S -0.1.16 MK2.5/3/S FW update -0.1.15 MK2/S/MMU FW update -0.1.14 Updated firmware versions for MK2.5 and MK3 -0.1.13 New MK2.5 and MK3 FW versions -0.1.12 New MK2.5 and MK3 FW versions -0.1.11 fw version changed to 3.3.1 -0.1.10 MK3 jerk and acceleration update -0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles -0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes -0.1.7 Fixed errors in 0.25mm and 0.6mm profiles -0.1.6 Split the MK2.5 profile from the MK2S -min_slic3r_version = 1.40.0-beta -0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles -0.1.4 edited fw version, added z-raise after print -min_slic3r_version = 1.40.0-alpha -0.1.3 Fixed an incorrect position of the max_print_height parameter -0.1.2 Wipe tower changes -0.1.1 Minor print speed adjustments -0.1.0 Initial +min_slic3r_version = 2.4.0-alpha0 +1.3.0-alpha0 Disabled thick bridges, updated support settings. +min_slic3r_version = 2.3.0-rc1 +1.2.4 Updated cost/density values in filament settings. Various changes in print settings. +1.2.3 Updated firmware version. Updated end g-code in MMU2 printer profiles. +1.2.2 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. +1.2.1 Updated FW version for MK2.5 family printers. +1.2.0 Added full_fan_speed_layer value for PETG. Increased support interface spacing for 0.6mm nozzle profiles. Updated firmware version. +min_slic3r_version = 2.3.0-beta2 +1.2.0-beta1 Updated end g-code. Added full_fan_speed_layer values. +min_slic3r_version = 2.3.0-beta0 +1.2.0-beta0 Adjusted infill anchor limits. Added filament spool weights. +min_slic3r_version = 2.3.0-alpha4 +1.2.0-alpha1 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. +1.2.0-alpha0 Added filament spool weights +min_slic3r_version = 2.2.0-alpha3 +1.1.13 Updated firmware version. Updated end g-code in MMU2 printer profiles. +1.1.12 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles. +1.1.11 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles. +1.1.10 Updated firmware version. +1.1.9 Updated K values in filament profiles (linear advance). Added new filament profiles and SLA materials. +1.1.8 Updated start/end g-code scripts for MK3 family printer profiles (reduced extruder motor current for some print profiles). Added new filament and SLA material profiles. +1.1.7 Updated end g-code for MMU2 Single printer profiles. Added/updated filament and SLA material profiles. +1.1.6 Updated firmware version for MK2.5/S and MK3/S. +1.1.5 Updated MMU1 specific retraction settings for Prusament PC Blend +1.1.4 Added Prusament PC Blend filament profile. +1.1.3 Added SLA material and filament profile +1.1.2 Added renamed_from fields for PETG filaments to indicate that they were renamed from PET. +1.1.1 Added Verbatim and Fiberlogy PETG filament profiles. Updated auto cooling settings for ABS. +1.1.1-beta Updated for PrusaSlicer 2.2.0-beta +1.1.1-alpha4 Extended list of default filaments to be installed, top/bottom_solid_min_thickness defined, infill_acceleration changed etc +1.1.1-alpha3 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer. +# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer, +# so they will see the print bed. +max_slic3r_version = 2.2.0-alpha2 +min_slic3r_version = 2.2.0-alpha0 +1.1.1-alpha2 Bumped up config version, so our in house customer will get updated profiles. +1.1.0 Filament aliases, Creality profiles and other goodies for PrusaSlicer 2.2.0-alpha0 +min_slic3r_version = 2.1.1-beta0 +1.0.11 Updated firmware version. +1.0.10 Updated firmware version for MK2.5/S and MK3/S. +1.0.9 Updated firmware version for MK2.5/S and MK3/S. +1.0.8 Various changes in FFF profiles, new filaments/materials added. See changelog. +1.0.7 Updated layer height limits for MINI +1.0.6 Added Prusa MINI profiles +min_slic3r_version = 2.1.0-alpha0 +1.0.5 Added SLA materials +1.0.4 Updated firmware version and 0.25mm nozzle profiles +1.0.3 Added filament profiles +1.0.2 Added SLA materials +1.0.1 Updated MK3 firmware version check to 3.8.0, new soluble support profiles for 0.6mm nozzle diameter MMU2S printers. +1.0.0 Updated end G-code for the MMU2 profiles to lift the extruder at the end of print. Wipe tower bridging distance was made smaller for soluble supports. +1.0.0-beta1 Updated color for the ASA filaments to differ from the other filaments. Single extruder printers now have no extruder color assigned, obects and toolpaths will be colored with the color of the active filament. +1.0.0-beta0 Printer model checks in start G-codes, ASA filament profiles, limits on min / max SL1 exposition times +1.0.0-alpha2 Printer model and nozzle diameter check +1.0.0-alpha1 Added Prusament ASA profile +1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX +min_slic3r_version = 1.42.0-alpha6 +0.8.10 Updated firmware version. +0.8.9 Updated firmware version for MK2.5/S and MK3/S. +0.8.8 Updated firmware version for MK2.5/S and MK3/S. +0.8.7 Updated firmware version +0.8.6 Updated firmware version for MK2.5/S and MK3/S +0.8.5 Updated SL1 printer and material settings +0.8.4 Added Prusament ASA profile +0.8.3 FW version and SL1 materials update +0.8.2 FFF and SL1 settings update +0.8.1 Output settings and SLA materials update +0.8.0 Updated for the PrusaSlicer 2.0.0 final release +0.8.0-rc2 Updated firmware versions for MK2.5/S and MK3/S +0.8.0-rc1 Updated SLA profiles +0.8.0-rc Updated for the PrusaSlicer 2.0.0-rc release +0.8.0-beta4 Updated SLA profiles +0.8.0-beta3 Updated SLA profiles +0.8.0-beta2 Updated SLA profiles +0.8.0-beta1 Updated SLA profiles +0.8.0-beta Updated SLA profiles +0.8.0-alpha9 Updated SLA and FFF profiles +0.8.0-alpha8 Updated SLA profiles +0.8.0-alpha7 Updated SLA profiles +0.8.0-alpha6 Updated SLA profiles +min_slic3r_version = 1.42.0-alpha +0.8.0-alpha Updated SLA profiles +0.4.0-alpha4 Updated SLA profiles +0.4.0-alpha3 Update of SLA profiles +0.4.0-alpha2 First SLA profiles +min_slic3r_version = 1.41.3-alpha +0.4.12 Updated firmware version for MK2.5/S and MK3/S. +0.4.11 Updated firmware version for MK2.5/S and MK3/S. +0.4.10 Updated firmware version +0.4.9 Updated firmware version for MK2.5/S and MK3/S +0.4.8 MK2.5/3/S FW update +0.4.7 MK2/S/MMU FW update +0.4.6 Updated firmware versions for MK2.5/S and MK3/S +0.4.5 Enabled remaining time support for MK2/S/MMU1 +0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.1 New MK2.5S and MK3S FW versions +0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +min_slic3r_version = 1.41.1 +0.3.11 Updated firmware version for MK2.5/S and MK3/S. +0.3.10 Updated firmware version +0.3.9 Updated firmware version for MK2.5/S and MK3/S +0.3.8 MK2.5/3/S FW update +0.3.7 MK2/S/MMU FW update +0.3.6 Updated firmware versions for MK2.5 and MK3 +0.3.5 New MK2.5 and MK3 FW versions +0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.3.3 Prusament PETG released +0.3.2 New MK2.5 and MK3 FW versions +0.3.1 New MK2.5 and MK3 FW versions +0.3.0 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.0-alpha +0.2.9 New MK2.5 and MK3 FW versions +0.2.8 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.1 +0.2.7 New MK2.5 and MK3 FW version +0.2.6 Added MMU2 MK2.5 settings +min_slic3r_version = 1.41.0-alpha +0.2.5 Prusament is out - added prusament settings +0.2.4 Added soluble support profiles for MMU2 +0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit +0.2.2 Edited MMU2 Single mode purge line +0.2.1 Added PET and BVOH settings for MMU2 +0.2.0-beta5 Fixed MMU1 ramming parameters +0.2.0-beta4 Added filament loading speed at start, increased minimal purge on wipe tower +0.2.0-beta3 Edited ramming parameters and filament cooling moves for MMU2 +0.2.0-beta2 Edited first layer speed and wipe tower position +0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles +0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets. +0.2.0-alpha7 Vojtech's fix the incorrect *MK3* references +0.2.0-alpha6 Jindra's way to fix the 0.2.0-alpha5 version +0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 +0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. +0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost +0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material +0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 +0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters +min_slic3r_version = 1.40.0 +0.1.18 Updated firmware version +0.1.17 Updated firmware version for MK2.5/S and MK3/S +0.1.16 MK2.5/3/S FW update +0.1.15 MK2/S/MMU FW update +0.1.14 Updated firmware versions for MK2.5 and MK3 +0.1.13 New MK2.5 and MK3 FW versions +0.1.12 New MK2.5 and MK3 FW versions +0.1.11 fw version changed to 3.3.1 +0.1.10 MK3 jerk and acceleration update +0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles +0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes +0.1.7 Fixed errors in 0.25mm and 0.6mm profiles +0.1.6 Split the MK2.5 profile from the MK2S +min_slic3r_version = 1.40.0-beta +0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles +0.1.4 edited fw version, added z-raise after print +min_slic3r_version = 1.40.0-alpha +0.1.3 Fixed an incorrect position of the max_print_height parameter +0.1.2 Wipe tower changes +0.1.1 Minor print speed adjustments +0.1.0 Initial diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 796d5782265..16737d15636 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,18 +5,15 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.2.4 +config_version = 1.3.0-alpha0 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. -#TODO: One day we may differentiate variants of the nozzles / hot ends, -#for example by the melt zone size, or whether the nozzle is hardened. # Printer model name will be shown by the installation wizard. - [printer_model:MINI] name = Original Prusa MINI && MINI+ variants = 0.4; 0.25; 0.6; 0.8 @@ -128,20 +125,21 @@ default_materials = Prusa Orange Tough @0.05 # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. -# Common print preset, mostly derived from MK2 single material with a 0.4mm nozzle. -# All other print presets will derive from the *common* print preset. +# Common print presets + [print:*common*] avoid_crossing_perimeters = 0 +thick_bridges = 0 bridge_acceleration = 1000 bridge_angle = 0 -bridge_flow_ratio = 0.8 -bridge_speed = 20 +bridge_flow_ratio = 1 +bridge_speed = 25 brim_width = 0 clip_multipart_objects = 1 compatible_printers = complete_objects = 0 default_acceleration = 1000 -dont_support_bridges = 1 +dont_support_bridges = 0 elefant_foot_compensation = 0.2 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear @@ -154,7 +152,7 @@ extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic -first_layer_acceleration = 1000 +first_layer_acceleration = 800 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 20 @@ -183,6 +181,7 @@ perimeter_extrusion_width = 0.45 post_process = print_settings_id = raft_layers = 0 +raft_first_layer_density = 90% resolution = 0 seam_position = nearest single_extruder_multi_material_priming = 1 @@ -203,7 +202,7 @@ support_material_interface_extruder = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_enforce_layers = 0 -support_material_contact_distance = 0.1 +support_material_contact_distance = 0.2 support_material_interface_contact_loops = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 @@ -212,9 +211,10 @@ support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 -support_material_threshold = 55 +support_material_threshold = 50 support_material_with_sheath = 0 -support_material_xy_spacing = 50% +support_material_xy_spacing = 60% +support_material_bottom_interface_layers = 0 thin_walls = 0 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 @@ -240,20 +240,15 @@ wipe_tower_x = 170 wipe_tower_y = 125 [print:*MK306*] +inherits = *MK3* fill_pattern = gyroid fill_density = 15% -single_extruder_multi_material_priming = 0 -travel_speed = 180 -wipe_tower_x = 170 -wipe_tower_y = 125 - -## MINI [print:*MINI*] fill_pattern = grid travel_speed = 150 wipe_tower = 0 -default_acceleration = 1250 +default_acceleration = 1000 first_layer_acceleration = 800 infill_acceleration = 1000 bridge_acceleration = 1000 @@ -262,7 +257,6 @@ max_print_speed = 150 extruder_clearance_height = 20 extruder_clearance_radius = 35 -# Print parameters common to a 0.25mm diameter nozzle. [print:*0.25nozzle*] elefant_foot_compensation = 0 external_perimeter_extrusion_width = 0.25 @@ -277,7 +271,11 @@ support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_spacing = 1 support_material_xy_spacing = 150% +support_material_contact_distance = 0.1 output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode +thick_bridges = 0 +bridge_flow_ratio = 1 +bridge_speed = 20 [print:*0.25nozzleMK3*] inherits = *0.25nozzle* @@ -288,7 +286,6 @@ infill_speed = 45 solid_infill_speed = 45 top_solid_infill_speed = 30 support_material_speed = 40 -bridge_speed = 20 gap_fill_speed = 30 perimeter_acceleration = 500 infill_acceleration = 1000 @@ -307,7 +304,6 @@ solid_infill_speed = 40 infill_acceleration = 800 first_layer_acceleration = 500 -# Print parameters common to a 0.6mm diameter nozzle. [print:*0.6nozzle*] external_perimeter_extrusion_width = 0.61 extrusion_width = 0.67 @@ -324,6 +320,31 @@ output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_heig infill_anchor_max = 15 top_solid_min_thickness = 0.9 bottom_solid_min_thickness = 0.6 +thick_bridges = 1 +bridge_flow_ratio = 0.95 +bridge_speed = 25 + +[print:*0.6nozzleMK3*] +inherits = *0.6nozzle* +external_perimeter_extrusion_width = 0.65 +extrusion_width = 0.65 +infill_extrusion_width = 0.65 +thick_bridges = 0 + +[print:*0.6nozzleMINI*] +inherits = *0.6nozzleMK3* +infill_extrusion_width = 0.68 +solid_infill_extrusion_width = 0.68 +fill_pattern = gyroid +fill_density = 15% +travel_speed = 150 +perimeter_acceleration = 800 +infill_acceleration = 1000 +bridge_acceleration = 1000 +first_layer_acceleration = 800 +default_acceleration = 1250 +support_material_speed = 40 +support_material_interface_speed = 100% [print:*0.8nozzle*] external_perimeter_extrusion_width = 0.9 @@ -356,34 +377,12 @@ bridge_flow_ratio = 0.9 perimeter_acceleration = 800 infill_acceleration = 1000 bridge_acceleration = 1000 -first_layer_acceleration = 1000 +first_layer_acceleration = 800 default_acceleration = 1000 top_solid_min_thickness = 1.2 bottom_solid_min_thickness = 0.8 single_extruder_multi_material_priming = 0 - -[print:*0.6nozzleMK3*] -inherits = *0.6nozzle* -external_perimeter_extrusion_width = 0.65 -extrusion_width = 0.65 -infill_extrusion_width = 0.65 -bridge_flow_ratio = 0.95 -bridge_speed = 25 - -[print:*0.6nozzleMINI*] -inherits = *0.6nozzleMK3* -infill_extrusion_width = 0.68 -solid_infill_extrusion_width = 0.68 -fill_pattern = gyroid -fill_density = 15% -travel_speed = 150 -perimeter_acceleration = 800 -infill_acceleration = 1000 -bridge_acceleration = 1000 -first_layer_acceleration = 1000 -default_acceleration = 1250 -support_material_speed = 40 -support_material_interface_speed = 100% +thick_bridges = 1 [print:*soluble_support*] overhangs = 1 @@ -399,29 +398,29 @@ support_material_threshold = 80 support_material_with_sheath = 1 wipe_tower_bridging = 6 support_material_interface_speed = 80% - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.05mm ---XXX -# XXXXXXXXXXXXXXXXXXXX +support_material_bottom_interface_layers = -1 +thick_bridges = 1 [print:*0.05mm*] inherits = *common* +layer_height = 0.05 bottom_solid_layers = 10 bridge_acceleration = 300 -bridge_flow_ratio = 0.7 +bridge_flow_ratio = 1.15 +bridge_speed = 15 default_acceleration = 1000 external_perimeter_speed = 20 fill_density = 20% -first_layer_acceleration = 500 +first_layer_acceleration = 800 gap_fill_speed = 20 infill_acceleration = 800 infill_speed = 30 max_print_speed = 80 small_perimeter_speed = 20 solid_infill_speed = 30 -support_material_extrusion_width = 0.3 +support_material_extrusion_width = 0.33 support_material_spacing = 1.5 -layer_height = 0.05 +support_material_contact_distance = 0.15 perimeter_acceleration = 300 perimeter_speed = 30 perimeters = 3 @@ -429,151 +428,28 @@ support_material_speed = 30 top_solid_infill_speed = 20 top_solid_layers = 15 -[print:0.05mm ULTRADETAIL] -inherits = *0.05mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 -infill_extrusion_width = 0.5 - -# MK3 # -[print:0.05mm ULTRADETAIL @MK3] -inherits = *0.05mm*; *MK3* -fill_pattern = gyroid -fill_density = 15% -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material -top_infill_extrusion_width = 0.4 - -# MK2 # -[print:0.05mm ULTRADETAIL @0.25 nozzle] -inherits = *0.05mm*; *0.25nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -fill_density = 20% -infill_speed = 20 -max_print_speed = 100 -perimeter_speed = 20 -small_perimeter_speed = 15 -solid_infill_speed = 20 -support_material_speed = 20 - -# MK3 # -[print:0.05mm ULTRADETAIL @0.25 nozzle MK3] -inherits = *0.05mm*; *0.25nozzle*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -fill_pattern = grid -fill_density = 20% - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.07mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - [print:*0.07mm*] -inherits = *common* -bottom_solid_layers = 8 -bridge_acceleration = 300 -bridge_flow_ratio = 0.7 -bridge_speed = 20 -default_acceleration = 1000 -external_perimeter_speed = 20 -fill_density = 15% -first_layer_acceleration = 500 -gap_fill_speed = 20 -infill_acceleration = 800 -infill_speed = 40 -max_print_speed = 80 -small_perimeter_speed = 20 -solid_infill_speed = 40 -support_material_extrusion_width = 0.3 -support_material_spacing = 1.5 +inherits = *0.05mm* layer_height = 0.07 -perimeter_acceleration = 300 -perimeter_speed = 30 -perimeters = 3 +bottom_solid_layers = 8 +bridge_flow_ratio = 1 +fill_density = 15% +infill_speed = 40 +solid_infill_speed = 40 support_material_speed = 40 top_solid_infill_speed = 30 top_solid_layers = 11 -# MK3 # -[print:0.07mm ULTRADETAIL @MK3] -inherits = *0.07mm*; *MK3* -fill_pattern = gyroid -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material -top_infill_extrusion_width = 0.4 - -[print:0.07mm ULTRADETAIL @0.25 nozzle MK3] -inherits = *0.07mm*; *0.25nozzle*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -infill_speed = 30 -solid_infill_speed = 30 -support_material_speed = 30 -top_solid_infill_speed = 20 -fill_pattern = grid -fill_density = 20% - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.10mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - -# MK2 # [print:*0.10mm*] inherits = *common* bottom_solid_layers = 7 -bridge_flow_ratio = 0.7 +bridge_flow_ratio = 1 +bridge_speed = 20 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 layer_height = 0.1 perimeter_acceleration = 800 top_solid_layers = 9 - -# MK2 # -[print:0.10mm DETAIL] -inherits = *0.10mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 -external_perimeter_speed = 40 -infill_acceleration = 2000 -infill_speed = 60 -perimeter_speed = 50 -solid_infill_speed = 50 -perimeters = 3 - -# MK3 # -[print:0.10mm DETAIL @MK3] -inherits = *0.10mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 25 -infill_acceleration = 1000 -infill_speed = 80 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 80 -top_infill_extrusion_width = 0.4 -top_solid_infill_speed = 40 -fill_pattern = gyroid -fill_density = 15% -perimeters = 3 - -# MK2 # -[print:0.10mm DETAIL @0.25 nozzle] -inherits = *0.10mm*; *0.25nozzle* -bridge_acceleration = 600 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 20 -infill_acceleration = 1000 -infill_speed = 40 -perimeter_acceleration = 600 -perimeter_speed = 25 -small_perimeter_speed = 15 -solid_infill_speed = 40 -top_solid_infill_speed = 30 - -# MK3 # -[print:0.10mm DETAIL @0.25 nozzle MK3] -inherits = *0.10mm*; *0.25nozzleMK3*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 -fill_pattern = grid -fill_density = 20% - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.15mm ---XXX -# XXXXXXXXXXXXXXXXXXXX +support_material_contact_distance = 0.17 [print:*0.15mm*] inherits = *common* @@ -587,138 +463,8 @@ perimeter_speed = 50 solid_infill_speed = 50 top_infill_extrusion_width = 0.4 top_solid_layers = 7 - -# MK2 # -[print:0.15mm 100mms Linear Advance] -inherits = *0.15mm* -bridge_flow_ratio = 0.95 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 50 -infill_speed = 100 -max_print_speed = 150 -perimeter_speed = 60 -small_perimeter_speed = 30 -solid_infill_speed = 100 -support_material_speed = 60 -top_solid_infill_speed = 70 - -# MK2 # -[print:0.15mm OPTIMAL] -inherits = *0.15mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -top_infill_extrusion_width = 0.45 - -# MK2 # -[print:0.15mm OPTIMAL @0.25 nozzle] -inherits = *0.15mm*; *0.25nozzle* -bridge_acceleration = 600 -bridge_flow_ratio = 0.7 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 20 -infill_acceleration = 1000 -infill_speed = 40 -perimeter_acceleration = 600 -perimeter_speed = 25 -small_perimeter_speed = 15 -solid_infill_speed = 40 -top_solid_infill_speed = 30 - -# MK2 # -[print:0.15mm OPTIMAL @0.6 nozzle] -inherits = *0.15mm*; *0.6nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -# MK3 # -[print:0.15mm QUALITY @MK3] -inherits = *0.15mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 25 -infill_acceleration = 1000 -infill_speed = 80 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 80 -top_solid_infill_speed = 40 -fill_pattern = gyroid -fill_density = 15% - -[print:0.15mm SPEED @MK3] -inherits = *0.15mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 60 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# MK3 MMU # -[print:0.15mm SOLUBLE FULL @MK3] -inherits = 0.15mm SPEED @MK3; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -support_material_extruder = 5 -support_material_interface_extruder = 5 -perimeter_speed = 40 -solid_infill_speed = 40 -infill_speed = 80 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 30 -support_material_speed = 45 - -# MK3 MMU # -[print:0.15mm SOLUBLE INTERFACE @MK3] -inherits = 0.15mm SOLUBLE FULL @MK3 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 - -# MK2 MMU # -[print:0.15mm OPTIMAL SOLUBLE FULL] -inherits = *0.15mm*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -external_perimeter_speed = 25 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -perimeter_speed = 40 -solid_infill_speed = 40 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 30 - -# MK2 MMU # -[print:0.15mm OPTIMAL SOLUBLE INTERFACE] -inherits = 0.15mm OPTIMAL SOLUBLE FULL -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 -support_material_xy_spacing = 80% - -# MK3 # -[print:0.15mm QUALITY @0.25 nozzle MK3] -inherits = *0.15mm*; *0.25nozzleMK3*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 -fill_pattern = grid -fill_density = 20% - -# MK3 # -[print:0.15mm DETAIL @0.6 nozzle MK3] -inherits = *0.15mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 -max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.20mm ---XXX -# XXXXXXXXXXXXXXXXXXXX +bridge_flow_ratio = 1 +bridge_speed = 25 [print:*0.20mm*] inherits = *common* @@ -734,114 +480,6 @@ solid_infill_speed = 50 top_infill_extrusion_width = 0.4 top_solid_layers = 5 -# MK2 # -[print:0.20mm 100mms Linear Advance] -inherits = *0.20mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 50 -infill_speed = 100 -max_print_speed = 150 -perimeter_speed = 60 -small_perimeter_speed = 30 -solid_infill_speed = 100 -support_material_speed = 60 -top_solid_infill_speed = 70 - -# MK3 # -[print:0.20mm QUALITY @MK3] -inherits = *0.20mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 25 -infill_acceleration = 1000 -infill_speed = 80 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 80 -top_solid_infill_speed = 40 -fill_pattern = gyroid -fill_density = 15% - -[print:0.20mm SPEED @MK3] -inherits = *0.20mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 60 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# MK3 MMU # -[print:0.20mm SOLUBLE FULL @MK3] -inherits = 0.20mm SPEED @MK3; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -support_material_extruder = 5 -support_material_interface_extruder = 5 -perimeter_speed = 40 -solid_infill_speed = 40 -infill_speed = 80 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 30 -support_material_speed = 45 - -# MK3 MMU # -[print:0.20mm SOLUBLE INTERFACE @MK3] -inherits = 0.20mm SOLUBLE FULL @MK3 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 - -# MK2 # -[print:0.20mm NORMAL] -inherits = *0.20mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 - -# MK2 # -[print:0.20mm NORMAL @0.6 nozzle] -inherits = *0.20mm*; *0.6nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -# MK2 MMU # -[print:0.20mm NORMAL SOLUBLE FULL] -inherits = *0.20mm*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -external_perimeter_speed = 30 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -perimeter_speed = 40 -solid_infill_speed = 40 -top_solid_infill_speed = 30 - -# MK2 MMU # -[print:0.20mm NORMAL SOLUBLE INTERFACE] -inherits = 0.20mm NORMAL SOLUBLE FULL -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 -support_material_xy_spacing = 80% - -# MK3 # -[print:0.20mm DETAIL @0.6 nozzle MK3] -inherits = *0.20mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 -max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 - - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.25mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - [print:*0.25mm*] inherits = *common* bottom_solid_layers = 4 @@ -852,10 +490,6 @@ layer_height = 0.25 perimeter_speed = 50 top_solid_layers = 4 -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.30mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - [print:*0.30mm*] inherits = *common* bottom_solid_layers = 4 @@ -869,65 +503,7 @@ perimeter_speed = 50 solid_infill_speed = 50 top_infill_extrusion_width = 0.4 top_solid_layers = 4 - -[print:0.30mm QUALITY @0.6 nozzle MK3] -inherits = *0.30mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 -max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 - -[print:0.30mm SOLUBLE FULL @0.6 nozzle MK3] -inherits = 0.30mm QUALITY @0.6 nozzle MK3; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -support_material_extruder = 5 -support_material_interface_extruder = 5 -support_material_speed = 40 -perimeter_speed = 40 -solid_infill_speed = 40 -top_infill_extrusion_width = 0.6 -support_material_extrusion_width = 0.6 -top_solid_infill_speed = 30 -support_material_xy_spacing = 80% - -[print:0.30mm SOLUBLE INTERFACE @0.6 nozzle MK3] -inherits = 0.30mm SOLUBLE FULL @0.6 nozzle MK3 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 - -[print:0.30mm DRAFT @MK3] -inherits = *0.30mm*; *MK3* -bottom_solid_layers = 3 -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 85 -max_print_speed = 200 -perimeter_speed = 50 -small_perimeter_speed = 30 -solid_infill_speed = 80 -top_solid_infill_speed = 40 -support_material_speed = 45 -external_perimeter_extrusion_width = 0.6 -extrusion_width = 0.5 -first_layer_extrusion_width = 0.42 -infill_extrusion_width = 0.5 -perimeter_extrusion_width = 0.5 -solid_infill_extrusion_width = 0.5 -top_infill_extrusion_width = 0.45 -support_material_extrusion_width = 0.38 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.35mm ---XXX -# XXXXXXXXXXXXXXXXXXXX +support_material_contact_distance = 0.3 [print:*0.35mm*] inherits = *common* @@ -946,65 +522,6 @@ solid_infill_speed = 60 top_solid_infill_speed = 50 top_solid_layers = 4 -# MK2 # -[print:0.35mm FAST] -inherits = *0.35mm* -bridge_flow_ratio = 0.95 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -first_layer_extrusion_width = 0.42 -perimeter_extrusion_width = 0.43 -solid_infill_extrusion_width = 0.7 -top_infill_extrusion_width = 0.43 -support_material_extrusion_width = 0.37 - -# MK2 # -[print:0.35mm FAST @0.6 nozzle] -inherits = *0.35mm*; *0.6nozzle* -# alias = 0.35mm FAST -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -# MK2 MMU # -[print:0.35mm FAST sol full @0.6 nozzle] -inherits = *0.35mm*; *0.6nozzle*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_model=="MK2SMM" and nozzle_diameter[0]==0.6 and num_extruders>1 -external_perimeter_extrusion_width = 0.6 -external_perimeter_speed = 30 -notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder -perimeter_speed = 40 -support_material_speed = 40 -support_material_interface_layers = 2 -support_material_xy_spacing = 120% -top_infill_extrusion_width = 0.6 -support_material_extrusion_width = 0.6 - -# MK2 MMU # -[print:0.35mm FAST sol int @0.6 nozzle] -inherits = 0.35mm FAST sol full @0.6 nozzle -support_material_extruder = 0 -support_material_interface_layers = 3 -support_material_with_sheath = 0 -support_material_xy_spacing = 150% - -# MK3 # -[print:0.35mm SPEED @0.6 nozzle MK3] -inherits = *0.35mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 -max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 -external_perimeter_extrusion_width = 0.68 -perimeter_extrusion_width = 0.68 -infill_extrusion_width = 0.68 -solid_infill_extrusion_width = 0.68 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.40mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - [print:*0.40mm*] inherits = *common* bottom_solid_layers = 3 @@ -1022,71 +539,236 @@ solid_infill_speed = 60 top_solid_infill_speed = 40 top_solid_layers = 4 -# MK3 # -[print:0.40mm DRAFT @0.6 nozzle MK3] -inherits = *0.40mm*; *0.6nozzleMK3*; *MK306* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1000 -infill_speed = 70 +## MK2 family ## + +## MK2 - 0.4mm nozzle +[print:0.05mm ULTRADETAIL] +inherits = *0.05mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 +infill_extrusion_width = 0.5 + +[print:0.10mm DETAIL] +inherits = *0.10mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +perimeter_speed = 50 +solid_infill_speed = 50 +perimeters = 3 +bridge_acceleration = 800 + +[print:0.15mm 100mms Linear Advance] +inherits = *0.15mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.15mm OPTIMAL] +inherits = *0.15mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +top_infill_extrusion_width = 0.45 + +[print:0.20mm 100mms Linear Advance] +inherits = *0.20mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.20mm NORMAL] +inherits = *0.20mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 + +[print:0.35mm FAST] +inherits = *0.35mm* +bridge_flow_ratio = 0.95 +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +first_layer_extrusion_width = 0.42 +perimeter_extrusion_width = 0.43 +solid_infill_extrusion_width = 0.7 +top_infill_extrusion_width = 0.45 +support_material_extrusion_width = 0.37 +support_material_contact_distance = 0.1 +top_solid_infill_speed = 40 +thick_bridges = 1 + +## MMU1 specific +[print:0.15mm OPTIMAL SOLUBLE FULL] +inherits = *0.15mm*; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +external_perimeter_speed = 25 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +bridge_flow_ratio = 0.8 +bridge_speed = 30 + +[print:0.15mm OPTIMAL SOLUBLE INTERFACE] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.20mm NORMAL SOLUBLE FULL] +inherits = *0.20mm*; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +external_perimeter_speed = 30 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +bridge_flow_ratio = 0.95 +bridge_speed = 30 + +[print:0.20mm NORMAL SOLUBLE INTERFACE] +inherits = 0.20mm NORMAL SOLUBLE FULL +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +## MK2 - 0.25mm nozzle + +[print:0.05mm ULTRADETAIL @0.25 nozzle] +inherits = *0.05mm*; *0.25nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +fill_density = 20% +infill_speed = 20 max_print_speed = 100 -perimeter_speed = 45 -solid_infill_speed = 70 -top_solid_infill_speed = 45 -external_perimeter_extrusion_width = 0.68 -perimeter_extrusion_width = 0.68 -infill_extrusion_width = 0.68 -solid_infill_extrusion_width = 0.68 +perimeter_speed = 20 +small_perimeter_speed = 15 +solid_infill_speed = 20 +support_material_speed = 20 +support_material_contact_distance = 0.07 -# XXXXXXXXXXXXXXXXXXXXXX -# XXX----- MK2.5 ----XXX -# XXXXXXXXXXXXXXXXXXXXXX +[print:0.10mm DETAIL @0.25 nozzle] +inherits = *0.10mm*; *0.25nozzle* +bridge_acceleration = 600 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 +external_perimeter_speed = 20 +infill_acceleration = 1000 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 15 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +support_material_contact_distance = 0.07 -# MK2.5 # -[print:0.15mm 100mms Linear Advance @MK2.5] -inherits = 0.15mm 100mms Linear Advance -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 +[print:0.15mm OPTIMAL @0.25 nozzle] +inherits = *0.15mm*; *0.25nozzle* +bridge_acceleration = 600 +bridge_flow_ratio = 0.8 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 +external_perimeter_speed = 20 +infill_acceleration = 1000 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 15 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +support_material_contact_distance = 0.08 -# MK2.5 # -[print:0.15mm OPTIMAL @MK2.5] -inherits = 0.15mm OPTIMAL -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 +## MK2 - 0.6mm nozzle + +[print:0.15mm OPTIMAL @0.6 nozzle] +inherits = *0.15mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +[print:0.20mm NORMAL @0.6 nozzle] +inherits = *0.20mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +[print:0.35mm FAST @0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +## MMU1 specific +[print:0.35mm FAST sol full @0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle*; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_model=="MK2SMM" and nozzle_diameter[0]==0.6 and num_extruders>1 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 30 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +perimeter_speed = 40 +support_material_speed = 40 +support_material_interface_layers = 2 +support_material_xy_spacing = 120% +top_infill_extrusion_width = 0.6 +support_material_extrusion_width = 0.6 + +[print:0.35mm FAST sol int @0.6 nozzle] +inherits = 0.35mm FAST sol full @0.6 nozzle +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% + +## MK2.5 -# MK2.5 MMU2 # [print:0.10mm DETAIL @MK2.5] inherits = 0.10mm DETAIL compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 single_extruder_multi_material_priming = 0 -# MK2.5 MMU2 # +[print:0.15mm 100mms Linear Advance @MK2.5] +inherits = 0.15mm 100mms Linear Advance +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.15mm OPTIMAL @MK2.5] +inherits = 0.15mm OPTIMAL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.20mm 100mms Linear Advance @MK2.5] +inherits = 0.20mm 100mms Linear Advance +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.20mm NORMAL @MK2.5] +inherits = 0.20mm NORMAL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.35mm FAST @MK2.5] +inherits = 0.35mm FAST +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +## MK2.5 - MMU2 specific + [print:0.15mm OPTIMAL SOLUBLE FULL @MK2.5] inherits = 0.15mm OPTIMAL SOLUBLE FULL support_material_extruder = 5 support_material_interface_extruder = 5 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -# MK2.5 MMU2 # [print:0.15mm OPTIMAL SOLUBLE INTERFACE @MK2.5] inherits = 0.15mm OPTIMAL SOLUBLE INTERFACE support_material_extruder = 0 support_material_interface_extruder = 5 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -# MK2.5 # -[print:0.20mm 100mms Linear Advance @MK2.5] -inherits = 0.20mm 100mms Linear Advance -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -# MK2.5 # -[print:0.20mm NORMAL @MK2.5] -inherits = 0.20mm NORMAL -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -# MK2.5 MMU2 # [print:0.20mm NORMAL SOLUBLE FULL @MK2.5] inherits = 0.20mm NORMAL SOLUBLE FULL support_material_extruder = 5 @@ -1094,7 +776,6 @@ support_material_interface_extruder = 5 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 single_extruder_multi_material_priming = 0 -# MK2.5 MMU2 # [print:0.20mm NORMAL SOLUBLE INTERFACE @MK2.5] inherits = 0.20mm NORMAL SOLUBLE INTERFACE support_material_extruder = 0 @@ -1102,14 +783,7 @@ support_material_interface_extruder = 5 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 single_extruder_multi_material_priming = 0 -# MK2.5 # -[print:0.35mm FAST @MK2.5] -inherits = 0.35mm FAST -# alias = 0.35mm FAST -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -# MK2.5 MMU2 0.6 nozzle # +# MK2.5 MMU2 0.6 nozzle [print:0.35mm SOLUBLE FULL @0.6 nozzle MK2.5] inherits = *0.35mm*; *0.6nozzle*; *soluble_support* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and printer_model!="MK2SMM" and nozzle_diameter[0]==0.6 and num_extruders>1 @@ -1132,12 +806,296 @@ support_material_interface_layers = 3 support_material_with_sheath = 0 support_material_xy_spacing = 80% -## 0.8mm nozzle print profiles +## MK3 family ## + +## MK3 - 0.4mm nozzle + +[print:0.05mm ULTRADETAIL @MK3] +inherits = *0.05mm*; *MK3* +fill_pattern = gyroid +fill_density = 15% +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +top_infill_extrusion_width = 0.4 + +[print:0.07mm ULTRADETAIL @MK3] +inherits = *0.07mm*; *MK3* +fill_pattern = gyroid +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +top_infill_extrusion_width = 0.4 + +[print:0.10mm DETAIL @MK3] +inherits = *0.10mm*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 25 +infill_acceleration = 1000 +bridge_acceleration = 800 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% +perimeters = 3 + +[print:0.15mm QUALITY @MK3] +inherits = *0.15mm*; *MK3* +bridge_speed = 25 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 25 +infill_acceleration = 1000 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% + +[print:0.15mm SPEED @MK3] +inherits = *0.15mm*; *MK3* +bridge_speed = 25 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 60 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +[print:0.20mm QUALITY @MK3] +inherits = *0.20mm*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 25 +infill_acceleration = 1000 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% + +[print:0.20mm SPEED @MK3] +inherits = *0.20mm*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 60 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +[print:0.30mm DRAFT @MK3] +inherits = *0.30mm*; *MK3* +bottom_solid_layers = 3 +bridge_speed = 25 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 85 +max_print_speed = 200 +perimeter_speed = 50 +small_perimeter_speed = 30 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +support_material_speed = 45 +external_perimeter_extrusion_width = 0.6 +extrusion_width = 0.5 +first_layer_extrusion_width = 0.42 +infill_extrusion_width = 0.5 +perimeter_extrusion_width = 0.5 +solid_infill_extrusion_width = 0.5 +top_infill_extrusion_width = 0.45 +support_material_extrusion_width = 0.38 +support_material_contact_distance = 0.2 + +## MK3 - MMU2 specific +[print:0.15mm SOLUBLE FULL @MK3] +inherits = 0.15mm SPEED @MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +perimeter_speed = 40 +solid_infill_speed = 40 +infill_speed = 80 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +support_material_speed = 45 +bridge_flow_ratio = 0.8 +bridge_speed = 30 + +[print:0.15mm SOLUBLE INTERFACE @MK3] +inherits = 0.15mm SOLUBLE FULL @MK3 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + +[print:0.20mm SOLUBLE FULL @MK3] +inherits = 0.20mm SPEED @MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +perimeter_speed = 40 +solid_infill_speed = 40 +infill_speed = 80 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +support_material_speed = 45 +bridge_flow_ratio = 0.95 +bridge_speed = 30 + +[print:0.20mm SOLUBLE INTERFACE @MK3] +inherits = 0.20mm SOLUBLE FULL @MK3 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + +## MK3 - 0.25mm nozzle + +[print:0.05mm ULTRADETAIL @0.25 nozzle MK3] +inherits = *0.05mm*; *0.25nozzle*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +fill_pattern = grid +fill_density = 20% +support_material_contact_distance = 0.07 + +[print:0.07mm ULTRADETAIL @0.25 nozzle MK3] +inherits = *0.07mm*; *0.25nozzle*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +infill_speed = 30 +solid_infill_speed = 30 +support_material_speed = 30 +top_solid_infill_speed = 20 +fill_pattern = grid +fill_density = 20% +support_material_contact_distance = 0.07 + +[print:0.10mm DETAIL @0.25 nozzle MK3] +inherits = *0.10mm*; *0.25nozzleMK3*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 +fill_pattern = grid +fill_density = 20% +support_material_contact_distance = 0.07 + +[print:0.15mm QUALITY @0.25 nozzle MK3] +inherits = *0.15mm*; *0.25nozzleMK3*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 +fill_pattern = grid +fill_density = 20% +support_material_contact_distance = 0.08 + +## MK3 - 0.6mm nozzle + +[print:0.15mm DETAIL @0.6 nozzle MK3] +inherits = *0.15mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +support_material_contact_distance = 0.22 +bridge_flow_ratio = 1 + +[print:0.20mm DETAIL @0.6 nozzle MK3] +inherits = *0.20mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +support_material_contact_distance = 0.22 +bridge_flow_ratio = 1 + +[print:0.30mm QUALITY @0.6 nozzle MK3] +inherits = *0.30mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 1 + +[print:0.35mm SPEED @0.6 nozzle MK3] +inherits = *0.35mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +external_perimeter_extrusion_width = 0.68 +perimeter_extrusion_width = 0.68 +infill_extrusion_width = 0.68 +solid_infill_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 0.95 + +[print:0.40mm DRAFT @0.6 nozzle MK3] +inherits = *0.40mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1000 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +external_perimeter_extrusion_width = 0.68 +perimeter_extrusion_width = 0.68 +infill_extrusion_width = 0.68 +solid_infill_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 0.95 + +## MK3 - MMU2 specific + +[print:0.30mm SOLUBLE FULL @0.6 nozzle MK3] +inherits = 0.30mm QUALITY @0.6 nozzle MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +support_material_speed = 40 +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.6 +support_material_extrusion_width = 0.6 +top_solid_infill_speed = 30 +support_material_xy_spacing = 80% + +[print:0.30mm SOLUBLE INTERFACE @0.6 nozzle MK3] +inherits = 0.30mm SOLUBLE FULL @0.6 nozzle MK3 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + +## 0.8mm nozzle - MK2.5 and MK3 +## Only for MMU2 Single mode at the moment [print:0.30mm DETAIL @0.8 nozzle] inherits = *common*; *0.8nozzle* layer_height = 0.30 -## Only for MMU2 Single mode at the moment compatible_printers_condition = printer_model=~/(MK3|MK2.5).*/ and nozzle_diameter[0]==0.8 and num_extruders==1 perimeter_speed = 35 external_perimeter_speed = 25 @@ -1151,7 +1109,6 @@ support_material_speed = 40 [print:0.40mm QUALITY @0.8 nozzle] inherits = *common*; *0.8nozzle* layer_height = 0.4 -## Only for MMU2 Single mode at the moment compatible_printers_condition = printer_model=~/(MK3|MK2.5).*/ and nozzle_diameter[0]==0.8 and num_extruders==1 perimeter_speed = 35 external_perimeter_speed = 25 @@ -1165,7 +1122,6 @@ support_material_speed = 40 [print:0.55mm DRAFT @0.8 nozzle] inherits = *common*; *0.8nozzle* layer_height = 0.55 -## Only for MMU2 Single mode at the moment compatible_printers_condition = printer_model=~/(MK3|MK2.5).*/ and nozzle_diameter[0]==0.8 and num_extruders==1 perimeter_speed = 30 external_perimeter_speed = 25 @@ -1179,9 +1135,9 @@ top_solid_infill_speed = 30 external_perimeter_extrusion_width = 1 perimeter_extrusion_width = 1 -## MINI print profiles +## MINI ## -# 0.4mm nozzle +# MINI - 0.4mm nozzle [print:0.05mm ULTRADETAIL @MINI] inherits = *0.05mm*; *MINI* @@ -1194,6 +1150,8 @@ perimeter_extrusion_width = 0.4 external_perimeter_extrusion_width = 0.4 support_material_xy_spacing = 60% support_material_speed = 30 +support_material_extrusion_width = 0.35 +bridge_acceleration = 300 [print:0.07mm ULTRADETAIL @MINI] inherits = *0.07mm*; *MINI* @@ -1205,10 +1163,13 @@ small_perimeter_speed = 15 perimeter_extrusion_width = 0.4 external_perimeter_extrusion_width = 0.4 support_material_xy_spacing = 60% +support_material_extrusion_width = 0.35 +bridge_acceleration = 300 [print:0.10mm DETAIL @MINI] inherits = *0.10mm*; *MINI* -bridge_speed = 30 +bridge_speed = 20 +bridge_acceleration = 700 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 40 external_perimeter_speed = 30 @@ -1219,12 +1180,10 @@ top_solid_infill_speed = 40 fill_pattern = gyroid fill_density = 15% perimeters = 3 -bridge_acceleration = 1000 support_material_xy_spacing = 60% [print:0.15mm QUALITY @MINI] inherits = *0.15mm*; *MINI* -bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 40 external_perimeter_speed = 30 @@ -1233,24 +1192,20 @@ solid_infill_speed = 80 top_solid_infill_speed = 40 fill_pattern = gyroid fill_density = 15% -bridge_flow_ratio = 0.85 support_material_xy_spacing = 60% [print:0.15mm SPEED @MINI] inherits = *0.15mm*; *MINI* -bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 50 external_perimeter_speed = 40 infill_speed = 140 solid_infill_speed = 140 top_solid_infill_speed = 40 -bridge_flow_ratio = 0.85 support_material_xy_spacing = 60% [print:0.20mm QUALITY @MINI] inherits = *0.20mm*; *MINI* -bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 40 external_perimeter_speed = 30 @@ -1263,7 +1218,6 @@ support_material_xy_spacing = 60% [print:0.20mm SPEED @MINI] inherits = *0.20mm*; *MINI* -bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 perimeter_speed = 50 external_perimeter_speed = 40 @@ -1275,7 +1229,8 @@ support_material_xy_spacing = 60% [print:0.25mm DRAFT @MINI] inherits = *0.25mm*; *MINI* -bridge_speed = 30 +bridge_speed = 25 +bridge_flow_ratio = 0.95 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 40 infill_speed = 110 @@ -1288,8 +1243,9 @@ infill_extrusion_width = 0.45 solid_infill_extrusion_width = 0.45 top_infill_extrusion_width = 0.4 support_material_xy_spacing = 60% +support_material_contact_distance = 0.2 -# 0.25mm nozzle +# MINI - 0.25mm nozzle [print:0.05mm ULTRADETAIL @0.25 nozzle MINI] inherits = *0.05mm*; *0.25nozzle*; *MINI* @@ -1297,6 +1253,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and fill_pattern = grid fill_density = 20% support_material_speed = 30 +support_material_contact_distance = 0.07 [print:0.07mm ULTRADETAIL @0.25 nozzle MINI] inherits = *0.07mm*; *0.25nozzle*; *MINI* @@ -1307,20 +1264,23 @@ support_material_speed = 30 top_solid_infill_speed = 20 fill_pattern = grid fill_density = 20% +support_material_contact_distance = 0.07 [print:0.10mm DETAIL @0.25 nozzle MINI] inherits = *0.10mm*; *0.25nozzleMINI*; *MINI* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.25 fill_pattern = grid fill_density = 20% +support_material_contact_distance = 0.07 [print:0.15mm QUALITY @0.25 nozzle MINI] inherits = *0.15mm*; *0.25nozzleMINI*; *MINI* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.25 fill_pattern = grid fill_density = 20% +support_material_contact_distance = 0.08 -# 0.6mm nozzle MINI +# MINI - 0.6mm nozzle [print:0.15mm DETAIL @0.6 nozzle MINI] inherits = *0.15mm*; *0.6nozzleMINI* @@ -1333,6 +1293,8 @@ solid_infill_speed = 70 top_solid_infill_speed = 45 infill_extrusion_width = 0.65 solid_infill_extrusion_width = 0.65 +support_material_contact_distance = 0.22 +bridge_flow_ratio = 1 [print:0.20mm DETAIL @0.6 nozzle MINI] inherits = *0.20mm*; *0.6nozzleMINI* @@ -1345,6 +1307,8 @@ solid_infill_speed = 70 top_solid_infill_speed = 45 infill_extrusion_width = 0.65 solid_infill_extrusion_width = 0.65 +support_material_contact_distance = 0.22 +bridge_flow_ratio = 1 [print:0.30mm QUALITY @0.6 nozzle MINI] inherits = *0.30mm*; *0.6nozzleMINI* @@ -1357,6 +1321,8 @@ solid_infill_speed = 65 top_solid_infill_speed = 45 external_perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 1 [print:0.35mm SPEED @0.6 nozzle MINI] inherits = *0.35mm*; *0.6nozzleMINI* @@ -1369,6 +1335,8 @@ solid_infill_speed = 60 top_solid_infill_speed = 45 external_perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 0.95 [print:0.40mm DRAFT @0.6 nozzle MINI] inherits = *0.40mm*; *0.6nozzleMINI* @@ -1383,8 +1351,10 @@ external_perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68 infill_extrusion_width = 0.68 solid_infill_extrusion_width = 0.68 +support_material_contact_distance = 0.25 +bridge_flow_ratio = 0.95 -# 0.8mm nozzle MINI +# MINI - 0.8mm nozzle [print:0.30mm DETAIL @0.8 nozzle MINI] inherits = 0.30mm DETAIL @0.8 nozzle @@ -1490,7 +1460,7 @@ max_fan_speed = 50 min_fan_speed = 30 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.04{else}0.08{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K24{elsif nozzle_diameter[0]==0.8};{else}M900 K45{endif} ; Filament gcode LA 1.0" temperature = 240 -filament_retract_length = 1.4 +filament_retract_length = 1 filament_retract_lift = 0.2 compatible_printers_condition = printer_model!="MK2SMM" and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) @@ -1500,14 +1470,14 @@ compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_model!="MK2S filament_max_volumetric_speed = 15 [filament:*PETMMU1*] -inherits = *PET* +; inherits = *PET* filament_retract_length = nil filament_retract_speed = nil filament_retract_lift = 0.2 compatible_printers_condition = printer_model=="MK2SMM" [filament:*PETMINI*] -inherits = *PET* +; inherits = *PET* filament_retract_length = nil filament_retract_speed = 40 filament_deretract_speed = 25 @@ -1518,7 +1488,7 @@ compatible_printers_condition = printer_model=="MINI" start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.6}0.12{elsif nozzle_diameter[0]==0.8}0.06{else}0.2{endif} ; Filament gcode" [filament:*PETMINI06*] -inherits = *PET* +; inherits = *PET* filament_retract_length = nil filament_retract_speed = 40 filament_deretract_speed = 25 @@ -1529,7 +1499,7 @@ start_filament_gcode = "M900 K0.12 ; Filament gcode" filament_max_volumetric_speed = 13 [filament:*ABSMINI*] -inherits = *ABS* +; inherits = *ABS* bed_temperature = 100 filament_retract_length = 2.7 filament_retract_speed = nil @@ -1861,8 +1831,8 @@ min_fan_speed = 20 max_fan_speed = 20 min_print_speed = 15 slowdown_below_layer_time = 15 -first_layer_temperature = 265 -temperature = 265 +first_layer_temperature = 260 +temperature = 260 filament_type = ASA [filament:Prusament ASA] @@ -2015,9 +1985,10 @@ filament_cost = 27.82 filament_density = 1.04 filament_spool_weight = 245 -[filament:Plasty Mladec ABS] +[filament:Filament PM ABS] inherits = *ABSC* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec ABS" +filament_vendor = Filament PM filament_cost = 27.82 filament_density = 1.08 filament_spool_weight = 230 @@ -2038,9 +2009,21 @@ filament_cost = 27.82 filament_density = 1.27 compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MK2SMM" and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) -[filament:Plasty Mladec PETG] +[filament:Extrudr PETG] inherits = *PET* -filament_vendor = Plasty Mladec +filament_vendor = Extrudr +filament_cost = 35.45 +filament_density = 1.29 +temperature = 220 +bed_temperature = 70 +first_layer_temperature = 220 +first_layer_bed_temperature = 70 +slowdown_below_layer_time = 20 + +[filament:Filament PM PETG] +inherits = *PET* +renamed_from = "Plasty Mladec PETG" +filament_vendor = Filament PM filament_cost = 27.82 filament_density = 1.27 filament_spool_weight = 230 @@ -2301,9 +2284,10 @@ filament_vendor = Made for Prusa filament_cost = 27.82 filament_spool_weight = 230 -[filament:Plasty Mladec ABS @MMU2] +[filament:Filament PM ABS @MMU2] inherits = *ABS MMU2* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec ABS @MMU2" +filament_vendor = Filament PM filament_density = 1.08 filament_cost = 27.82 filament_spool_weight = 230 @@ -2402,9 +2386,10 @@ filament_density = 1.27 filament_spool_weight = 201 filament_type = PETG -[filament:Plasty Mladec PETG @0.6 nozzle] +[filament:Filament PM PETG @0.6 nozzle] inherits = *PET06* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec PETG @0.6 nozzle" +filament_vendor = Filament PM first_layer_temperature = 230 temperature = 240 filament_cost = 27.82 @@ -2458,7 +2443,7 @@ filament_unload_time = 12 filament_unloading_speed = 20 filament_unloading_speed_start = 120 filament_loading_speed_start = 19 -filament_retract_length = 1.4 +filament_retract_length = 1 filament_retract_lift = 0.2 [filament:*PET MMU2 06*] @@ -2471,9 +2456,10 @@ inherits = *PET MMU2* renamed_from = "Generic PET MMU2"; "Generic PETG MMU2" filament_vendor = Generic -[filament:Plasty Mladec PETG @MMU2] +[filament:Filament PM PETG @MMU2] inherits = *PET MMU2* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec PETG @MMU2" +filament_vendor = Filament PM filament_spool_weight = 230 [filament:Prusa PETG @MMU2] @@ -2510,10 +2496,11 @@ filament_cost = 36.29 filament_density = 1.27 filament_spool_weight = 201 -[filament:Plasty Mladec PETG @MMU2 0.6 nozzle] +[filament:Filament PM PETG @MMU2 0.6 nozzle] inherits = *PET MMU2 06* +renamed_from = "Plasty Mladec PETG @MMU2 0.6 nozzle" filament_type = PETG -filament_vendor = Plasty Mladec +filament_vendor = Filament PM filament_spool_weight = 230 [filament:Prusa PLA] @@ -2530,9 +2517,10 @@ filament_vendor = Fiberlogy filament_cost = 25.4 filament_density = 1.24 -[filament:Plasty Mladec PLA] +[filament:Filament PM PLA] inherits = *PLA* -filament_vendor = Plasty Mladec +renamed_from = "Plasty Mladec PLA" +filament_vendor = Filament PM filament_cost = 27.82 filament_density = 1.24 filament_spool_weight = 230 @@ -2576,6 +2564,26 @@ filament_vendor = EUMAKERS filament_cost = 25.4 filament_density = 1.24 +[filament:Extrudr PLA NX1] +inherits = *PLA* +filament_vendor = Extrudr +filament_cost = 22.76 +filament_density = 1.24 +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +full_fan_speed_layer = 3 +max_fan_speed = 90 +min_fan_speed = 30 +slowdown_below_layer_time = 20 + +[filament:Extrudr PLA NX2] +inherits = Extrudr PLA NX1 +filament_vendor = Extrudr +filament_cost = 23.63 +filament_density = 1.3 + [filament:Floreon3D PLA] inherits = *PLA* filament_vendor = Floreon3D @@ -2930,204 +2938,75 @@ temperature = 220 ## Filaments MMU1 [filament:ColorFabb HT @MMU1] -inherits = *PETMMU1* -filament_vendor = ColorFabb -bed_temperature = 110 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_cost = 58.66 -filament_density = 1.18 -filament_spool_weight = 236 -first_layer_bed_temperature = 105 -first_layer_temperature = 270 -max_fan_speed = 20 -min_fan_speed = 10 +inherits = ColorFabb HT; *PETMMU1* start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" -temperature = 270 [filament:ColorFabb XT @MMU1] -inherits = *PETMMU1* -filament_vendor = ColorFabb -filament_type = PETG -filament_cost = 62.90 -filament_density = 1.27 -filament_spool_weight = 236 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 270 +inherits = ColorFabb XT; *PETMMU1* [filament:ColorFabb XT-CF20 @MMU1] -inherits = *PETMMU1* -filament_vendor = ColorFabb -compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MK2SMM" -extrusion_multiplier = 1.05 -filament_cost = 80.65 -filament_density = 1.35 -filament_spool_weight = 236 -filament_colour = #804040 -filament_max_volumetric_speed = 2 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 +inherits = ColorFabb XT-CF20; *PETMMU1* start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" -temperature = 260 [filament:ColorFabb nGen @MMU1] -inherits = *PETMMU1* -filament_vendor = ColorFabb -filament_cost = 21.2 -filament_density = 1.2 -filament_spool_weight = 236 -bridge_fan_speed = 40 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_type = NGEN -first_layer_temperature = 240 -max_fan_speed = 35 -min_fan_speed = 20 +inherits = ColorFabb nGen; *PETMMU1* [filament:E3D Edge @MMU1] -inherits = *PETMMU1* -filament_vendor = E3D -filament_cost = 56.9 -filament_density = 1.26 -filament_type = EDGE +inherits = E3D Edge; *PETMMU1* [filament:Fillamentum CPE @MMU1] -inherits = *PETMMU1* -filament_vendor = Fillamentum -filament_cost = 56.45 -filament_density = 1.25 -filament_spool_weight = 230 -filament_type = CPE -first_layer_bed_temperature = 90 -first_layer_temperature = 275 -max_fan_speed = 50 -min_fan_speed = 50 -disable_fan_first_layers = 3 -full_fan_speed_layer = 5 -temperature = 275 +inherits = Fillamentum CPE; *PETMMU1* [filament:Generic PETG @MMU1] -inherits = *PETMMU1* +inherits = Generic PETG; *PETMMU1* renamed_from = "Generic PET MMU1"; "Generic PETG MMU1" -filament_vendor = Generic -filament_cost = 27.82 -filament_density = 1.27 [filament:Devil Design PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Devil Design -filament_cost = 20.99 -filament_density = 1.23 -filament_spool_weight = 250 -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 +inherits = Devil Design PETG; *PETMMU1* -[filament:Plasty Mladec PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Plasty Mladec -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 +[filament:Filament PM PETG @MMU1] +inherits = Filament PM PETG; *PETMMU1* +renamed_from = "Plasty Mladec PETG @MMU1" [filament:Verbatim PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Verbatim -filament_cost = 27.90 -filament_density = 1.27 -filament_spool_weight = 235 +inherits = Verbatim PETG; *PETMMU1* [filament:Fiberlogy PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Fiberlogy -filament_cost = 21.50 -filament_density = 1.27 +inherits = Fiberlogy PETG; *PETMMU1* [filament:Prusa PETG @MMU1] -inherits = *PETMMU1* +inherits = Prusa PETG; *PETMMU1* renamed_from = "Prusa PET MMU1"; "Prusa PETG MMU1" -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 [filament:Prusament PETG @MMU1] -inherits = *PETMMU1* -filament_vendor = Prusa Polymers -first_layer_temperature = 240 -temperature = 250 -filament_cost = 36.29 -filament_density = 1.27 -filament_spool_weight = 201 -filament_type = PETG +inherits = Prusament PETG; *PETMMU1* + +[filament:Extrudr PETG @MMU1] +inherits = Extrudr PETG; *PETMMU1* +filament_vendor = Extrudr [filament:Taulman T-Glase @MMU1] -inherits = *PETMMU1* -filament_vendor = Taulman -filament_cost = 40 -filament_density = 1.27 -bridge_fan_speed = 40 -cooling = 0 -fan_always_on = 0 -first_layer_bed_temperature = 90 -first_layer_temperature = 240 -max_fan_speed = 5 -min_fan_speed = 0 +inherits = Taulman T-Glase; *PETMMU1* start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" [filament:Fiberthree F3 PA Pure Pro @MMU1] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 200.84 -filament_density = 1.2 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 +inherits = Fiberthree F3 PA Pure Pro filament_max_volumetric_speed = 4 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 20 -min_fan_speed = 20 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.04{else}0.05{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K18{elsif nozzle_diameter[0]==0.8};{else}M900 K30{endif} ; Filament gcode LA 1.0" +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +filament_retract_before_travel = nil +filament_wipe = nil compatible_printers_condition = printer_model=="MK2SMM" [filament:Fiberthree F3 PA-CF Pro @MMU1] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 208.1 -filament_density = 1.25 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 275 -temperature = 275 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 +inherits = Fiberthree F3 PA-CF Pro filament_max_volumetric_speed = 4 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 0 -min_fan_speed = 0 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.04{else}0.05{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K18{elsif nozzle_diameter[0]==0.8};{else}M900 K30{endif} ; Filament gcode LA 1.0" +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +filament_retract_before_travel = nil +filament_wipe = nil compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MK2SMM" [filament:Fiberthree F3 PA-GF Pro @MMU1] @@ -3166,108 +3045,47 @@ compatible_printers_condition = printer_model=="MK2SMM" [filament:Generic PETG @MINI] inherits = Generic PETG; *PETMINI* renamed_from = "Generic PET MINI"; "Generic PETG MINI" -filament_vendor = Generic -filament_cost = 27.82 -filament_density = 1.27 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Devil Design PETG @MINI] -inherits = Generic PETG; *PETMINI* -filament_vendor = Devil Design -filament_cost = 20.99 -filament_density = 1.23 -filament_spool_weight = 250 -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 +inherits = Devil Design PETG; *PETMINI* compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.6 -[filament:Plasty Mladec PETG @MINI] -inherits = Generic PETG; *PETMINI* -filament_vendor = Plasty Mladec -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 +[filament:Filament PM PETG @MINI] +inherits = Filament PM PETG; *PETMINI* +renamed_from = "Plasty Mladec PETG @MINI" compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.6 [filament:Verbatim PETG @MINI] -inherits = Generic PETG; *PETMINI* -filament_vendor = Verbatim -filament_cost = 27.90 -filament_density = 1.27 -filament_spool_weight = 235 +inherits = Verbatim PETG; *PETMINI* compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.6 [filament:Fiberlogy PETG @MINI] -inherits = Generic PETG; *PETMINI* -filament_vendor = Fiberlogy -filament_cost = 21.50 -filament_density = 1.27 +inherits = Fiberlogy PETG; *PETMINI* compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.6 [filament:Generic ABS @MINI] inherits = Generic ABS; *ABSMINI* -filament_vendor = Generic -filament_cost = 27.82 -filament_density = 1.08 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 [filament:Fiberthree F3 PA Pure Pro @MINI] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 200.84 -filament_density = 1.2 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 270 -temperature = 270 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 1 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 +inherits = Fiberthree F3 PA Pure Pro filament_max_volumetric_speed = 4 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 20 -min_fan_speed = 20 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.04{else}0.05{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K18{elsif nozzle_diameter[0]==0.8};{else}M900 K30{endif} ; Filament gcode LA 1.0" +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +filament_retract_before_travel = nil +filament_wipe = nil compatible_printers_condition = printer_model=="MINI" [filament:Fiberthree F3 PA-CF Pro @MINI] -inherits = *common* -filament_vendor = Fiberthree -filament_cost = 208.1 -filament_density = 1.25 -bed_temperature = 70 -first_layer_bed_temperature = 75 -first_layer_temperature = 275 -temperature = 275 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 20 -min_print_speed = 15 -slowdown_below_layer_time = 10 -filament_colour = #DEE0E6 +inherits = Fiberthree F3 PA-CF Pro filament_max_volumetric_speed = 4 -filament_soluble = 0 -filament_type = NYLON -max_fan_speed = 0 -min_fan_speed = 0 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.04{else}0.05{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K18{elsif nozzle_diameter[0]==0.8};{else}M900 K30{endif} ; Filament gcode LA 1.0" +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +filament_retract_before_travel = nil +filament_wipe = nil compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI" [filament:Fiberthree F3 PA-GF Pro @MINI] @@ -3280,14 +3098,8 @@ max_fan_speed = 15 min_fan_speed = 15 [filament:Kimya ABS Carbon @MINI] -inherits = *ABSMINI* -filament_vendor = Kimya -filament_cost = 140.4 -filament_density = 1.032 -filament_colour = #804040 +inherits = Kimya ABS Carbon; *ABSMINI* filament_max_volumetric_speed = 6 -first_layer_temperature = 260 -temperature = 260 compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI" [filament:Kimya ABS Kevlar @MINI] @@ -3296,146 +3108,57 @@ filament_vendor = Kimya filament_density = 1.037 [filament:Esun ABS @MINI] -inherits = Generic ABS; *ABSMINI* -filament_vendor = Esun -filament_cost = 27.82 -filament_density = 1.01 -filament_spool_weight = 265 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +inherits = Esun ABS; *ABSMINI* [filament:Hatchbox ABS @MINI] -inherits = Generic ABS; *ABSMINI* -filament_vendor = Hatchbox -filament_cost = 27.82 -filament_density = 1.08 -filament_spool_weight = 245 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +inherits = Hatchbox ABS; *ABSMINI* -[filament:Plasty Mladec ABS @MINI] -inherits = Generic ABS; *ABSMINI* -filament_vendor = Plasty Mladec -filament_cost = 27.82 -filament_density = 1.08 -filament_spool_weight = 230 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +[filament:Filament PM ABS @MINI] +inherits = Filament PM ABS; *ABSMINI* +renamed_from = "Plasty Mladec ABS @MINI" [filament:Verbatim ABS @MINI] -inherits = Generic ABS; *ABSMINI* -filament_vendor = Verbatim -filament_cost = 25.87 -filament_density = 1.05 -filament_spool_weight = 235 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +inherits = Verbatim ABS; *ABSMINI* [filament:Prusament PETG @MINI] inherits = Prusament PETG; *PETMINI* -filament_vendor = Prusa Polymers -first_layer_temperature = 240 -temperature = 250 -filament_density = 1.27 -filament_spool_weight = 201 -filament_cost = 36.29 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +[filament:Extrudr PETG @MINI] +inherits = Extrudr PETG; *PETMINI* +filament_vendor = Extrudr + [filament:Kimya PETG Carbon @MINI] -inherits = *PETMINI* -filament_vendor = Kimya -extrusion_multiplier = 1.05 -filament_cost = 150.02 -filament_density = 1.317 -filament_colour = #804040 +inherits = Kimya PETG Carbon; *PETMINI* filament_max_volumetric_speed = 6 -first_layer_bed_temperature = 85 -first_layer_temperature = 240 -temperature = 240 filament_retract_length = nil filament_retract_lift = 0.3 compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI" [filament:Prusament PETG @0.6 nozzle MINI] inherits = Prusament PETG; *PETMINI06* -first_layer_temperature = 240 -temperature = 250 -filament_density = 1.27 -filament_spool_weight = 201 -filament_cost = 36.29 [filament:Generic PETG @0.6 nozzle MINI] inherits = Generic PETG; *PETMINI06* renamed_from = "Generic PET 0.6 nozzle MINI"; "Generic PETG 0.6 nozzle MINI" -filament_cost = 27.82 -filament_density = 1.27 [filament:Devil Design PETG @0.6 nozzle MINI] -inherits = Generic PETG; *PETMINI06* -filament_vendor = Devil Design -first_layer_temperature = 230 -first_layer_bed_temperature = 85 -temperature = 230 -bed_temperature = 90 -filament_cost = 20.99 -filament_density = 1.23 -filament_spool_weight = 250 +inherits = Devil Design PETG; *PETMINI06* -[filament:Plasty Mladec PETG @0.6 nozzle MINI] -inherits = Generic PETG; *PETMINI06* -filament_vendor = Plasty Mladec -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 +[filament:Filament PM PETG @0.6 nozzle MINI] +inherits = Filament PM PETG; *PETMINI06* +renamed_from = "Plasty Mladec PETG @0.6 nozzle MINI" [filament:Verbatim PETG @0.6 nozzle MINI] -inherits = Generic PETG; *PETMINI06* -filament_vendor = Verbatim -filament_spool_weight = 235 +inherits = Verbatim PETG; *PETMINI06* [filament:Fiberlogy PETG @0.6 nozzle MINI] -inherits = Generic PETG; *PETMINI06* -filament_vendor = Fiberlogy +inherits = Fiberlogy PETG; *PETMINI06* [filament:Prusament ASA @MINI] inherits = Prusament ASA; *ABSMINI* -first_layer_temperature = 260 first_layer_bed_temperature = 100 -temperature = 260 bed_temperature = 100 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -slowdown_below_layer_time = 15 -disable_fan_first_layers = 4 -filament_type = ASA -filament_colour = #FFF2EC -filament_cost = 42.69 -filament_density = 1.07 -filament_spool_weight = 201 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 [filament:Fillamentum Flexfill 98A @MINI] @@ -3541,89 +3264,43 @@ inherits = Fillamentum CPE; *PETMINI* first_layer_temperature = 265 first_layer_bed_temperature = 90 temperature = 265 -filament_type = CPE -filament_cost = 56.45 -filament_density = 1.25 -filament_spool_weight = 230 disable_fan_first_layers = 3 full_fan_speed_layer = 5 [filament:ColorFabb nGen @MINI] inherits = ColorFabb nGen; *PETMINI* -filament_cost = 52.46 -filament_density = 1.2 -filament_spool_weight = 236 [filament:E3D PC-ABS @MINI] inherits = E3D PC-ABS; *ABSMINI* -filament_density = 1.05 -filament_cost = 28.80 +filament_retract_length = nil +filament_retract_before_travel = nil +filament_wipe = nil [filament:Fillamentum ABS @MINI] inherits = Fillamentum ABS; *ABSMINI* -filament_cost = 32.4 -filament_density = 1.04 -filament_spool_weight = 230 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 [filament:Fillamentum ASA @MINI] inherits = Fillamentum ASA; *ABSMINI* -first_layer_temperature = 255 first_layer_bed_temperature = 100 -temperature = 255 bed_temperature = 100 -fan_always_on = 1 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -min_print_speed = 15 -slowdown_below_layer_time = 15 -disable_fan_first_layers = 4 -filament_type = ASA -filament_colour = #FFF2EC -filament_cost = 38.7 -filament_density = 1.07 -filament_spool_weight = 230 [filament:Polymaker PC-Max @MINI] inherits = Polymaker PC-Max; *ABSMINI* -filament_type = PC filament_max_volumetric_speed = 7 bed_temperature = 100 -filament_colour = #FFF2EC first_layer_bed_temperature = 100 first_layer_temperature = 270 temperature = 270 -bridge_fan_speed = 0 -filament_cost = 77.3 -filament_density = 1.20 +filament_retract_length = nil +filament_retract_before_travel = nil +filament_wipe = nil [filament:Prusament PC Blend @MINI] -inherits = *ABSMINI* -filament_vendor = Prusa Polymers -filament_cost = 60.49 -filament_density = 1.22 -filament_spool_weight = 201 -fan_always_on = 0 +inherits = Prusament PC Blend; *ABSMINI* first_layer_temperature = 275 first_layer_bed_temperature = 100 temperature = 275 bed_temperature = 100 -cooling = 1 -min_fan_speed = 20 -max_fan_speed = 20 -bridge_fan_speed = 30 -min_print_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -filament_type = PC -filament_colour = #DEE0E6 filament_max_volumetric_speed = 7 filament_retract_length = nil filament_retract_speed = nil @@ -3634,117 +3311,43 @@ filament_wipe = nil compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 [filament:Prusa ABS @MINI] -inherits = *ABSMINI* -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.08 -filament_spool_weight = 230 -fan_always_on = 0 -cooling = 1 -min_fan_speed = 15 -max_fan_speed = 15 -disable_fan_first_layers = 4 -fan_below_layer_time = 30 -bridge_fan_speed = 25 +inherits = Prusa ABS; *ABSMINI* compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 [filament:Generic HIPS @MINI] -inherits = *ABSMINI* -filament_vendor = Generic -filament_cost = 27.3 -filament_density = 1.04 -bridge_fan_speed = 50 -cooling = 1 -extrusion_multiplier = 1 -fan_always_on = 1 -fan_below_layer_time = 10 -filament_colour = #FFFFD7 -filament_soluble = 1 -filament_type = HIPS -first_layer_temperature = 230 -max_fan_speed = 20 -min_fan_speed = 20 -temperature = 230 +inherits = Generic HIPS; *ABSMINI* [filament:ColorFabb HT @MINI] -inherits = *PETMINI* -filament_vendor = ColorFabb +inherits = ColorFabb HT; *PETMINI* bed_temperature = 100 -bridge_fan_speed = 30 -cooling = 1 -disable_fan_first_layers = 3 -fan_always_on = 0 -fan_below_layer_time = 10 -filament_cost = 58.66 -filament_density = 1.18 -filament_spool_weight = 236 first_layer_bed_temperature = 100 -first_layer_temperature = 270 -max_fan_speed = 20 -min_fan_speed = 10 -temperature = 270 +min_fan_speed = 15 [filament:ColorFabb XT @MINI] -inherits = *PETMINI* -filament_vendor = ColorFabb -filament_type = PETG -filament_cost = 62.90 -filament_density = 1.27 -filament_spool_weight = 236 +inherits = ColorFabb XT; *PETMINI* first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 270 [filament:ColorFabb XT-CF20 @MINI] -inherits = *PETMINI* -filament_vendor = ColorFabb +inherits = ColorFabb XT-CF20; *PETMINI* compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI" -extrusion_multiplier = 1.05 -filament_cost = 80.65 -filament_density = 1.35 -filament_spool_weight = 236 -filament_colour = #804040 -filament_max_volumetric_speed = 2 first_layer_bed_temperature = 90 first_layer_temperature = 260 temperature = 260 [filament:Taulman T-Glase @MINI] -inherits = *PETMINI* -filament_vendor = Taulman -filament_cost = 40 -filament_density = 1.27 -bridge_fan_speed = 40 -cooling = 0 -fan_always_on = 0 -first_layer_bed_temperature = 90 -first_layer_temperature = 240 -max_fan_speed = 5 -min_fan_speed = 0 +inherits = Taulman T-Glase; *PETMINI* [filament:E3D Edge @MINI] -inherits = *PETMINI* -filament_vendor = E3D -filament_cost = 56.9 -filament_density = 1.26 -filament_type = EDGE +inherits = E3D Edge; *PETMINI* [filament:Prusa PETG @MINI] -inherits = *PETMINI* +inherits = Prusa PETG; *PETMINI* renamed_from = "Prusa PET MINI"; "Prusa PETG MINI" -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 [filament:Prusa PETG @0.6 nozzle MINI] -inherits = *PETMINI06* +inherits = Prusa PETG; *PETMINI06* renamed_from = "Prusa PET 0.6 nozzle MINI"; "Prusa PETG 0.6 nozzle MINI" -filament_vendor = Made for Prusa -filament_cost = 27.82 -filament_density = 1.27 -filament_spool_weight = 230 ## Filaments 0.8 nozzle @@ -5321,7 +4924,7 @@ retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.2.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.2.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-2 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 @@ -5356,19 +4959,19 @@ printer_model = MK2SMM [printer:*mm-single*] inherits = *multimaterial* -end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; turn off fan\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n\n +end_gcode = G1 E-4 F2100\nG91\nG1 Z1 F7200\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7\nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3\nG1 E-15 F5000\nG1 E-50 F5400\nG1 E-15 F3000\nG1 E-12 F2000\nG1 F1600\nG1 X0 Y1 E3\nG1 X50 Y1 E-5\nG1 F2000\nG1 X0 Y1 E5\nG1 X50 Y1 E-5\nG1 F2400\nG1 X0 Y1 E5\nG1 X50 Y1 E-5\nG1 F2400\nG1 X0 Y1 E5\nG1 X50 Y1 E-3\nG4 S0\nM107 ; turn off fan\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n\n printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.2.3 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.2.3 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0\nM203 E100\nM92 E140\nG1 Z0.25 F7200\nG1 X50 E80 F1000\nG1 X160 E20 F1000\nG1 Z0.2 F7200\nG1 X220 E13 F1000\nG1 X240 E0 F1000\nG92 E0 default_print_profile = 0.15mm OPTIMAL [printer:*mm-multi*] inherits = *multimaterial* high_current_on_filament_swap = 1 -end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; turn off fan\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors +end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100\nG91\nG1 Z1 F7200\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7\nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3\nG1 E-15 F5000\nG1 E-50 F5400\nG1 E-15 F3000\nG1 E-12 F2000\nG1 F1600\nG1 X0 Y1 E3\nG1 X50 Y1 E-5\nG1 F2000\nG1 X0 Y1 E5\nG1 X50 Y1 E-5\nG1 F2400\nG1 X0 Y1 E5\nG1 X50 Y1 E-5\nG1 F2400\nG1 X0 Y1 E5\nG1 X50 Y1 E-3\nG4 S0\n{endif}\nM107 ; turn off fan\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors extruder_colour = #FFAA55;#E37BA0;#4ECDD3;#FB7259 nozzle_diameter = 0.4,0.4,0.4,0.4 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.2.3 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\n{endif}\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.2.3 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.25 F7200\nG1 X50 E80 F1000\nG1 X160 E20 F1000\nG1 Z0.2 F7200\nG1 X220 E13 F1000\nG1 X240 E0 F1000\n{endif}\nG92 E0 default_print_profile = 0.15mm OPTIMAL # XXXXXXXXXXXXXXXXX @@ -5438,21 +5041,21 @@ inherits = Original Prusa i3 MK2S printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2S 0.25 nozzle printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2S 0.6 nozzle printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 [printer:Original Prusa i3 MK2.5 0.8 nozzle] inherits = Original Prusa i3 MK2S 0.6 nozzle @@ -5464,39 +5067,20 @@ min_layer_height = 0.2 retract_length = 1 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle [printer:Original Prusa i3 MK2.5 MMU2 Single] -inherits = Original Prusa i3 MK2.5; *mm2* +inherits = *25mm2* printer_model = MK2.5MMU2 single_extruder_multi_material = 0 max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 4.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL @MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\n; select extruder\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; load to nozzle\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n -end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\n; select extruder\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; load to nozzle\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.20 F1000\nG1 X5 E4 F1000\nG92 E0\n +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors [printer:Original Prusa i3 MK2.5 MMU2 Single 0.8 nozzle] inherits = Original Prusa i3 MK2.5S MMU2S Single 0.8 nozzle @@ -5511,87 +5095,43 @@ inherits = Original Prusa i3 MK2.5S MMU2S Single 0.25 nozzle printer_model = MK2.5MMU2 [printer:Original Prusa i3 MK2.5 MMU2] -inherits = Original Prusa i3 MK2.5; *mm2* +inherits = *25mm2* printer_model = MK2.5MMU2 max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 4.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL @MK2.5 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n single_extruder_multi_material = 1 -# The 5x nozzle diameter defines the number of extruders. Other extruder parameters -# (for example the retract values) are duplicaed from the first value, so they do not need -# to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n -end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\nG92 E0\n +end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5S] inherits = Original Prusa i3 MK2.5 printer_model = MK2.5S -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S 0.25 nozzle] inherits = Original Prusa i3 MK2.5 0.25 nozzle printer_model = MK2.5S -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S 0.6 nozzle] inherits = Original Prusa i3 MK2.5 0.6 nozzle printer_model = MK2.5S -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S 0.8 nozzle] inherits = Original Prusa i3 MK2.5 0.8 nozzle printer_model = MK2.5S [printer:Original Prusa i3 MK2.5S MMU2S Single] -inherits = Original Prusa i3 MK2.5; *mm2s* +inherits = *25mm2s* printer_model = MK2.5SMMU2S single_extruder_multi_material = 0 max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 4.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL @MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n -end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors [printer:Original Prusa i3 MK2.5S MMU2S Single 0.8 nozzle] inherits = Original Prusa i3 MK2.5S MMU2S Single @@ -5601,7 +5141,7 @@ min_layer_height = 0.2 nozzle_diameter = 0.8 printer_variant = 0.8 retract_length = 1 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -5623,41 +5163,19 @@ nozzle_diameter = 0.25 printer_variant = 0.25 retract_lift = 0.15 default_print_profile = 0.10mm DETAIL 0.25 nozzle -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n [printer:Original Prusa i3 MK2.5S MMU2S] -inherits = Original Prusa i3 MK2.5; *mm2s* +inherits = *25mm2s* printer_model = MK2.5SMMU2S max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 4.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL @MK2.5 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n single_extruder_multi_material = 1 -# The 5x nozzle diameter defines the number of extruders. Other extruder parameters -# (for example the retract values) are duplicaed from the first value, so they do not need -# to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n -end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\nG92 E0\n +end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM900 K0 ; reset LA\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5S MMU2S 0.6 nozzle] inherits = Original Prusa i3 MK2.5S MMU2S @@ -5675,7 +5193,7 @@ min_layer_height = 0.15 printer_variant = 0.6 default_print_profile = 0.20mm NORMAL @0.6 nozzle -## For later use. 0.8mm nozzle profiles are only available for MMU2 Single mode at the moment. +## 0.8mm nozzle profiles are only available for MMU2 Single mode at the moment. ## [printer:Original Prusa i3 MK2.5S MMU2S 0.8 nozzle] ## inherits = Original Prusa i3 MK2.5S MMU2S @@ -5725,7 +5243,7 @@ remaining_times = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} printer_model = MK3 default_print_profile = 0.15mm QUALITY @MK3 @@ -5736,7 +5254,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E8 F700 ; intro line\nG1 X100 E12.5 F700 ; intro line\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] @@ -5745,7 +5263,7 @@ nozzle_diameter = 0.6 max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif} default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 [printer:Original Prusa i3 MK3 0.8 nozzle] @@ -5755,7 +5273,7 @@ max_layer_height = 0.6 min_layer_height = 0.2 printer_variant = 0.8 retract_length = 1 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0\nM221 S95 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -5763,13 +5281,11 @@ default_filament_profile = Prusament PLA @0.8 nozzle inherits = Original Prusa i3 MK3 renamed_from = "Original Prusa i3 MK3S" printer_model = MK3S -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} [printer:Original Prusa i3 MK3S & MK3S+ 0.25 nozzle] inherits = Original Prusa i3 MK3 0.25 nozzle renamed_from = "Original Prusa i3 MK3S 0.25 nozzle" printer_model = MK3S -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} [printer:Original Prusa i3 MK3S & MK3S+ 0.6 nozzle] inherits = Original Prusa i3 MK3 0.6 nozzle @@ -5804,12 +5320,32 @@ printer_model = MK3SMMU2S default_print_profile = 0.15mm QUALITY @MK3 default_filament_profile = Prusament PLA @MMU2 +[printer:*25mm2*] +inherits = Original Prusa i3 MK2.5 +single_extruder_multi_material = 1 +cooling_tube_length = 10 +cooling_tube_retraction = 30 +parking_pos_retraction = 85 +retract_length_toolchange = 3 +extra_loading_move = -13 +default_filament_profile = Prusament PLA @MMU2 + +[printer:*25mm2s*] +inherits = Original Prusa i3 MK2.5S +single_extruder_multi_material = 1 +cooling_tube_length = 20 +cooling_tube_retraction = 40 +parking_pos_retraction = 85 +retract_length_toolchange = 3 +extra_loading_move = -25 +default_filament_profile = Prusament PLA @MMU2 + [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} -end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2 Single 0.6 nozzle] inherits = Original Prusa i3 MK3 MMU2 Single @@ -5818,7 +5354,7 @@ nozzle_diameter = 0.6 max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 [printer:Original Prusa i3 MK3 MMU2 Single 0.8 nozzle] @@ -5829,7 +5365,7 @@ max_layer_height = 0.6 min_layer_height = 0.2 printer_variant = 0.8 retract_length = 1 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -5841,27 +5377,24 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F1000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F1000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 [printer:Original Prusa i3 MK3 MMU2] inherits = *mm2* -# The 5x nozzle diameter defines the number of extruders. Other extruder parameters -# (for example the retract values) are duplicaed from the first value, so they do not need -# to be defined explicitely. machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} -end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM84 ; disable motors\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK3S & MK3S+ MMU2S Single] inherits = *mm2s* renamed_from = "Original Prusa i3 MK3S MMU2S Single" single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} -end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)}{endif} F720 ; Move print head up\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors [printer:Original Prusa i3 MK3S & MK3S+ MMU2S Single 0.6 nozzle] inherits = Original Prusa i3 MK3S & MK3S+ MMU2S Single @@ -5871,7 +5404,7 @@ nozzle_diameter = 0.6 max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 [printer:Original Prusa i3 MK3S & MK3S+ MMU2S Single 0.8 nozzle] @@ -5882,7 +5415,7 @@ max_layer_height = 0.6 min_layer_height = 0.2 printer_variant = 0.8 retract_length = 1 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -5895,7 +5428,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 [printer:Original Prusa i3 MK3S & MK3S+ MMU2S] @@ -5904,8 +5437,8 @@ renamed_from = "Original Prusa i3 MK3S MMU2S" machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} -end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM84 ; disable motors\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +end_gcode = ; Lift print head a bit\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; Move print head up\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nG1 X0 Y210 F3000 ; home X axis\nM84 ; disable motors\n ## 0.6mm nozzle MMU2/S printer profiles @@ -5916,7 +5449,7 @@ nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 [printer:Original Prusa i3 MK3 MMU2 0.6 nozzle] @@ -5925,7 +5458,7 @@ nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 ## 0.8mm nozzle MMU2/S printer profiles @@ -5938,7 +5471,7 @@ default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 ## max_layer_height = 0.6 ## min_layer_height = 0.2 ## printer_variant = 0.8 -## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM115 U3.9.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 ## default_print_profile = 0.40mm QUALITY @0.8 nozzle ## [printer:Original Prusa i3 MK3S & MK3S+ MMU2S 0.8 nozzle] @@ -5963,10 +5496,11 @@ thumbnails = 16x16,220x124 bed_shape = 0x0,180x0,180x180,0x180 default_filament_profile = "Prusament PLA" default_print_profile = 0.15mm QUALITY @MINI -gcode_flavor = marlin +gcode_flavor = marlinfirmware machine_max_acceleration_e = 5000 machine_max_acceleration_extruding = 1250 machine_max_acceleration_retracting = 1250 +machine_max_acceleration_travel = 1250 machine_max_acceleration_x = 1250 machine_max_acceleration_y = 1250 machine_max_acceleration_z = 400 @@ -5996,7 +5530,7 @@ retract_lift_below = 179 retract_layer_change = 1 silent_mode = 0 remaining_times = 1 -start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0.0\nG1 Y-2.0 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110.0 E8.0 F900\nG1 X40.0 E10.0 F700\nG92 E0.0\n\nM221 S95 ; set flow +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0\nG1 Y-2 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110 E8 F900\nG1 X40 E10 F700\nG92 E0\n\nM221 S95 ; set flow end_gcode = G1 E-1 F2100 ; retract\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+2, max_print_height)}{endif} F720 ; Move print head up\nG1 X178 Y178 F4200 ; park print head\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} F720 ; Move print head further up\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM221 S100 ; reset flow\nM900 K0 ; reset LA\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MINI\n extruder_colour = @@ -6012,7 +5546,7 @@ default_print_profile = 0.10mm DETAIL @0.25 nozzle MINI retract_length = 3 retract_lift = 0.15 retract_before_travel = 1 -start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0.0\nG1 Y-2.0 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110.0 E8.0 F600\nG1 X40.0 E10.0 F400\nG92 E0.0\n\nM221 S95 ; set flow +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0\nG1 Y-2 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110 E8 F600\nG1 X40 E10 F400\nG92 E0\n\nM221 S95 ; set flow [printer:Original Prusa MINI & MINI+ 0.6 nozzle] inherits = Original Prusa MINI & MINI+ From 06726145a5f23aac5a974079901a75074f179764 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 14 Apr 2021 07:38:50 +0200 Subject: [PATCH 088/154] Show info about custom supports and seam in ObjectList Slight refactoring in GLGizmosManager so it is easier to open a gizmo from the ObjectList --- resources/icons/info.png | Bin 0 -> 308 bytes src/slic3r/GUI/GUI_ObjectList.cpp | 68 +++++++++++++++--- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 7 +- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 8 ++- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 45 +++++++----- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 2 + src/slic3r/GUI/ObjectDataViewModel.cpp | 72 ++++++++++++++++++- src/slic3r/GUI/ObjectDataViewModel.hpp | 18 ++++- 9 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 resources/icons/info.png diff --git a/resources/icons/info.png b/resources/icons/info.png new file mode 100644 index 0000000000000000000000000000000000000000..9eeee9b3cdbac7fd819d3f4c3ee85414c5f1ed50 GIT binary patch literal 308 zcmV-40n7f0P)6|`_Rtm|7y^1~Ew=AW5=n0W(fT;mbLc<=CvuP!m`yl+rpgAzqp8NMnw zN!+2v9C;$1N30I+zs_}ZYEa-QtZ4bm;QmokMfEnO_zs(PV)Mv3c0VL!_bF(`IW#IJ zSafGP0SinrjIiz@J%^>R#;Ci-vyowo@ddeKY%{FzAieuODeleteWarningIcon(m_objects_model->GetParent(item)); m_objects_model->Delete(item); + update_info_items(obj_idx); } void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) @@ -2118,20 +2119,32 @@ void ObjectList::part_selection_changed() { if (item) { - if (m_objects_model->GetParent(item) == wxDataViewItem(nullptr)) { - obj_idx = m_objects_model->GetIdByItem(item); + const ItemType type = m_objects_model->GetItemType(item); + const wxDataViewItem parent = m_objects_model->GetParent(item); + const ItemType parent_type = m_objects_model->GetItemType(parent); + obj_idx = m_objects_model->GetObjectIdByItem(item); + + if (parent == wxDataViewItem(nullptr) + || type == itInfo) { og_name = _(L("Object manipulation")); m_config = &(*m_objects)[obj_idx]->config; update_and_show_manipulations = true; + + if (type == itInfo) { + Unselect(item); + assert(parent_type == itObject); + Select(parent); + InfoItemType info_type = m_objects_model->GetInfoItemType(item); + GLGizmosManager::EType gizmo_type = + info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports + : GLGizmosManager::EType::Seam; + GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); + if (gizmos_mgr.get_current_type() != gizmo_type) + gizmos_mgr.open_gizmo(gizmo_type); + } } else { - obj_idx = m_objects_model->GetObjectIdByItem(item); - - const ItemType type = m_objects_model->GetItemType(item); if (type & itSettings) { - const auto parent = m_objects_model->GetParent(item); - const ItemType parent_type = m_objects_model->GetItemType(parent); - if (parent_type & itObject) { og_name = _(L("Object Settings to modify")); m_config = &(*m_objects)[obj_idx]->config; @@ -2243,6 +2256,38 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D return ret; } + +void ObjectList::update_info_items(size_t obj_idx) +{ + const ModelObject* model_object = (*m_objects)[obj_idx]; + wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx); + assert(item_obj.IsOk()); + + for (InfoItemType type : {InfoItemType::CustomSupports, InfoItemType::CustomSeam}) { + wxDataViewItem item = m_objects_model->GetInfoItemByType(item_obj, type); + bool shows = item.IsOk(); + bool should_show = printer_technology() == ptFFF + && std::any_of(model_object->volumes.begin(), model_object->volumes.end(), + [type](const ModelVolume* mv) { + return ! (type == InfoItemType::CustomSupports + ? mv->supported_facets.empty() + : mv->seam_facets.empty()); + }); + + if (! shows && should_show) { + m_objects_model->AddInfoChild(item_obj, type); + Expand(item_obj); + } + else if (shows && ! should_show) { + Unselect(item); + m_objects_model->Delete(item); + Select(item_obj); + } + } +} + + + void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; @@ -2251,6 +2296,8 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) model_object->config.has("extruder") ? model_object->config.extruder() : 0, get_mesh_errors_count(obj_idx) > 0); + update_info_items(obj_idx); + // add volumes to the object if (model_object->volumes.size() > 1) { for (const ModelVolume* volume : model_object->volumes) { @@ -3029,7 +3076,7 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); - if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) + if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer | itInfo)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); else add_to_selection(item, selection, instance_idx, mode); @@ -3442,6 +3489,9 @@ void ObjectList::update_object_list_by_printer_technology() m_objects_model->GetChildren(wxDataViewItem(nullptr), object_items); for (auto& object_item : object_items) { + // update custom supports info + update_info_items(m_objects_model->GetObjectIdByItem(object_item)); + // Update Settings Item for object update_settings_item_and_selection(object_item, sel); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index d70c53849d9..5812e26f756 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -18,7 +18,6 @@ class wxBoxSizer; class wxBitmapComboBox; class wxMenuItem; -class ObjectDataViewModel; class MenuWithSeparators; namespace Slic3r { @@ -347,6 +346,7 @@ public: void update_and_show_object_settings_item(); void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); void update_object_list_by_printer_technology(); + void update_info_items(size_t obj_idx); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); void instances_to_separated_objects(const int obj_idx); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index f3f87cc33e5..d870687129c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -7,6 +7,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" #include @@ -316,8 +317,12 @@ void GLGizmoFdmSupports::update_model_object() const updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); } - if (updated) + if (updated) { + const ModelObjectPtrs& mos = wxGetApp().model().objects; + wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 6c3a8ddd3a8..97247675504 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -7,7 +7,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" - +#include "slic3r/GUI/GUI_ObjectList.hpp" #include @@ -222,8 +222,12 @@ void GLGizmoSeam::update_model_object() const updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get()); } - if (updated) + if (updated) { + const ModelObjectPtrs& mos = wxGetApp().model().objects; + wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 9dc785b3fac..2f1396d6346 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -163,6 +163,17 @@ void GLGizmosManager::reset_all_states() m_hover = Undefined; } +bool GLGizmosManager::open_gizmo(EType type) +{ + int idx = int(type); + if (m_gizmos[idx]->is_selectable() && m_gizmos[idx]->is_activable()) { + activate_gizmo(m_current == idx ? Undefined : (EType)idx); + update_data(); + return true; + } + return false; +} + void GLGizmosManager::set_hover_id(int id) { if (!m_enabled || m_current == Undefined) @@ -266,24 +277,21 @@ bool GLGizmosManager::is_running() const bool GLGizmosManager::handle_shortcut(int key) { - if (!m_enabled) + if (!m_enabled || m_parent.get_selection().is_empty()) return false; - if (m_parent.get_selection().is_empty()) + auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(), + [key](const std::unique_ptr& gizmo) { + int gizmo_key = gizmo->get_shortcut_key(); + return gizmo->is_selectable() + && ((gizmo_key == key - 64) || (gizmo_key == key - 96)); + }); + + if (it == m_gizmos.end()) return false; - bool handled = false; - - for (size_t idx : get_selectable_idxs()) { - int it_key = m_gizmos[idx]->get_shortcut_key(); - - if (m_gizmos[idx]->is_activable() && ((it_key == key - 64) || (it_key == key - 96))) { - activate_gizmo(m_current == idx ? Undefined : (EType)idx); - handled = true; - } - } - - return handled; + EType gizmo_type = EType(it - m_gizmos.begin()); + return open_gizmo(gizmo_type); } bool GLGizmosManager::is_dragging() const @@ -814,10 +822,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) if (!processed && !evt.HasModifiers()) { if (handle_shortcut(keyCode)) - { - update_data(); processed = true; - } } if (processed) @@ -1156,5 +1161,11 @@ bool GLGizmosManager::is_in_editing_mode(bool error_notification) const return true; } + +int GLGizmosManager::get_shortcut_key(GLGizmosManager::EType type) const +{ + return m_gizmos[type]->get_shortcut_key(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 917a5830c26..c8951966e6c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -171,6 +171,7 @@ public: void refresh_on_off_state(); void reset_all_states(); bool is_serializing() const { return m_serializing; } + bool open_gizmo(EType type); void set_hover_id(int id); void enable_grabber(EType type, unsigned int id, bool enable); @@ -228,6 +229,7 @@ public: void update_after_undo_redo(const UndoRedo::Snapshot& snapshot); int get_selectable_icons_cnt() const { return get_selectable_idxs().size(); } + int get_shortcut_key(GLGizmosManager::EType) const; private: void render_background(float left, float top, float right, float bottom, float border) const; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index cf7b7b47967..395e329e8b6 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -47,6 +47,19 @@ void ObjectDataViewModelNode::init_container() static constexpr char LayerRootIcon[] = "edit_layers_all"; static constexpr char LayerIcon[] = "edit_layers_some"; static constexpr char WarningIcon[] = "exclamation"; +static constexpr char InfoIcon[] = "info"; + +ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const InfoItemType info_type) : + m_parent(parent), + m_type(itInfo), + m_extruder(wxEmptyString) +{ + m_name = info_type == InfoItemType::CustomSupports + ? _L("Paint-on supports") + : _L("Paint-on seam"); + m_info_item_type = info_type; +} + ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : m_parent(parent), @@ -69,6 +82,8 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_bmp = create_scaled_bitmap(LayerRootIcon); // FIXME: pass window ptr m_name = _(L("Layers")); } + else if (type == itInfo) + assert(false); if (type & (itInstanceRoot | itLayerRoot)) init_container(); @@ -250,6 +265,7 @@ ObjectDataViewModel::ObjectDataViewModel() m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); + m_info_bmp = create_scaled_bitmap(InfoIcon); } ObjectDataViewModel::~ObjectDataViewModel() @@ -330,13 +346,44 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent return child; } +wxDataViewItem ObjectDataViewModel::AddInfoChild(const wxDataViewItem &parent_item, InfoItemType info_type) +{ + ObjectDataViewModelNode *root = static_cast(parent_item.GetID()); + if (!root) return wxDataViewItem(0); + + const auto node = new ObjectDataViewModelNode(root, info_type); + + // The new item should be added according to its order in InfoItemType. + // Find last info item with lower index and append after it. + const auto& children = root->GetChildren(); + int idx = -1; + for (int i=0; iGetType() == itInfo && int(children[i]->GetInfoItemType()) < int(info_type) ) + idx = i; + } + + root->Insert(node, idx+1); + node->SetBitmap(m_info_bmp); + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + return child; +} + wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &parent_item) { ObjectDataViewModelNode *root = static_cast(parent_item.GetID()); if (!root) return wxDataViewItem(0); const auto node = new ObjectDataViewModelNode(root, itSettings); - root->Insert(node, 0); + + // In case there are some info items, append after them. + size_t i = 0; + for (i = 0; iGetChildCount(); ++i) + if (root->GetNthChild(i)->GetType() != itInfo) + break; + + root->Insert(node, i); // notify control const wxDataViewItem child((void*)node); ItemAdded(parent_item, child); @@ -1379,6 +1426,14 @@ ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const return node->m_type < 0 ? itUndef : node->m_type; } +InfoItemType ObjectDataViewModel::GetInfoItemType(const wxDataViewItem &item) const +{ + if (!item.IsOk()) + return InfoItemType::Undef; + ObjectDataViewModelNode *node = static_cast(item.GetID()); + return node->m_info_item_type; +} + wxDataViewItem ObjectDataViewModel::GetItemByType(const wxDataViewItem &parent_item, ItemType type) const { if (!parent_item.IsOk()) @@ -1411,6 +1466,21 @@ wxDataViewItem ObjectDataViewModel::GetLayerRootItem(const wxDataViewItem &item) return GetItemByType(item, itLayerRoot); } +wxDataViewItem ObjectDataViewModel::GetInfoItemByType(const wxDataViewItem &parent_item, InfoItemType type) const +{ + if (! parent_item.IsOk()) + return wxDataViewItem(0); + + ObjectDataViewModelNode *node = static_cast(parent_item.GetID()); + for (size_t i = 0; i < node->GetChildCount(); i++) { + const ObjectDataViewModelNode* child_node = node->GetNthChild(i); + if (child_node->m_type == itInfo && child_node->m_info_item_type == type) + return wxDataViewItem((void*)child_node); + } + + return wxDataViewItem(0); // not found +} + bool ObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const { if (!item.IsOk()) diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index c23ec195bd7..cf440f5dc4f 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -27,6 +27,7 @@ enum ItemType { itSettings = 16, itLayerRoot = 32, itLayer = 64, + itInfo = 128 }; enum ColumnNumber @@ -44,6 +45,13 @@ enum PrintIndicator piUnprintable , // unprintable }; +enum class InfoItemType +{ + Undef, + CustomSupports, + CustomSeam +}; + class ObjectDataViewModelNode; WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); @@ -69,6 +77,7 @@ class ObjectDataViewModelNode std::string m_action_icon_name = ""; ModelVolumeType m_volume_type; + InfoItemType m_info_item_type {InfoItemType::Undef}; public: ObjectDataViewModelNode(const wxString& name, @@ -104,6 +113,7 @@ public: const wxString& extruder = wxEmptyString ); ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const InfoItemType type); ~ObjectDataViewModelNode() { @@ -176,6 +186,7 @@ public: const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } + InfoItemType GetInfoItemType() const { return m_info_item_type; } void SetIdx(const int& idx); int GetIdx() const { return m_idx; } ModelVolumeType GetVolumeType() { return m_volume_type; } @@ -244,6 +255,7 @@ class ObjectDataViewModel :public wxDataViewModel std::vector m_objects; std::vector m_volume_bmps; wxBitmap m_warning_bmp; + wxBitmap m_info_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -261,6 +273,7 @@ public: const int extruder = 0, const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); + wxDataViewItem AddInfoChild(const wxDataViewItem &parent_item, InfoItemType info_type); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector& print_indicator); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); @@ -335,12 +348,15 @@ public: // In our case it is an item with all columns bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } - ItemType GetItemType(const wxDataViewItem &item) const ; + ItemType GetItemType(const wxDataViewItem &item) const; + InfoItemType GetInfoItemType(const wxDataViewItem &item) const; wxDataViewItem GetItemByType( const wxDataViewItem &parent_item, ItemType type) const; wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const; wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const; wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const; + wxDataViewItem GetInfoItemByType(const wxDataViewItem &parent_item, InfoItemType type) const; + bool IsSettingsItem(const wxDataViewItem &item) const; void UpdateSettingsDigest( const wxDataViewItem &item, const std::vector& categories); From 2be5cb84424febbade8814404e0054c220baa149 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 19 Apr 2021 11:51:56 +0200 Subject: [PATCH 089/154] Info in ObjectList: Settings should be above the new info items, info items are selectable --- src/slic3r/GUI/GUI_ObjectList.cpp | 13 ++++++++----- src/slic3r/GUI/ObjectDataViewModel.cpp | 8 +------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dbfc05a6d30..652fcc65b4e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1525,14 +1525,20 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) if (type == itUndef) return; + wxDataViewItem parent = m_objects_model->GetParent(item); + if (type & itSettings) - del_settings_from_config(m_objects_model->GetParent(item)); + del_settings_from_config(parent); else if (type & itInstanceRoot && obj_idx != -1) del_instances_from_object(obj_idx); else if (type & itLayerRoot && obj_idx != -1) del_layers_from_object(obj_idx); else if (type & itLayer && obj_idx != -1) del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); + else if (type & itInfo && obj_idx != -1) { + Unselect(item); + Select(parent); + } else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) @@ -1540,7 +1546,7 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) // If last volume item with warning was deleted, unmark object item if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) - m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); + m_objects_model->DeleteWarningIcon(parent); m_objects_model->Delete(item); update_info_items(obj_idx); @@ -2131,9 +2137,6 @@ void ObjectList::part_selection_changed() update_and_show_manipulations = true; if (type == itInfo) { - Unselect(item); - assert(parent_type == itObject); - Select(parent); InfoItemType info_type = m_objects_model->GetInfoItemType(item); GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 395e329e8b6..541c2f8a9e6 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -377,13 +377,7 @@ wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &paren const auto node = new ObjectDataViewModelNode(root, itSettings); - // In case there are some info items, append after them. - size_t i = 0; - for (i = 0; iGetChildCount(); ++i) - if (root->GetNthChild(i)->GetType() != itInfo) - break; - - root->Insert(node, i); + root->Insert(node, 0); // notify control const wxDataViewItem child((void*)node); ItemAdded(parent_item, child); From d9053008ab12cea193356d23cdc4b6c31819f5e4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 19 Apr 2021 15:42:57 +0200 Subject: [PATCH 090/154] Info in ObjectList: Added variable layer height --- src/slic3r/GUI/GLCanvas3D.cpp | 4 +++ src/slic3r/GUI/GUI_ObjectList.cpp | 45 ++++++++++++++++++-------- src/slic3r/GUI/ObjectDataViewModel.cpp | 6 ++-- src/slic3r/GUI/ObjectDataViewModel.hpp | 3 +- src/slic3r/GUI/Plater.cpp | 6 ++++ src/slic3r/GUI/Plater.hpp | 1 + 6 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7bbdc72b115..2149f21e7c9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -508,6 +508,7 @@ void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) m_layer_height_profile.clear(); m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); } void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor) @@ -517,6 +518,7 @@ void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); } void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params) @@ -526,6 +528,7 @@ void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); } void GLCanvas3D::LayersEditing::generate_layer_height_texture() @@ -565,6 +568,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Manual edit"))); const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); } } m_layer_height_profile_modified = false; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 652fcc65b4e..15c4578d82a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2138,12 +2138,15 @@ void ObjectList::part_selection_changed() if (type == itInfo) { InfoItemType info_type = m_objects_model->GetInfoItemType(item); - GLGizmosManager::EType gizmo_type = - info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports - : GLGizmosManager::EType::Seam; - GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); - if (gizmos_mgr.get_current_type() != gizmo_type) - gizmos_mgr.open_gizmo(gizmo_type); + if (info_type != InfoItemType::VariableLayerHeight) { + GLGizmosManager::EType gizmo_type = + info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports + : GLGizmosManager::EType::Seam; + GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); + if (gizmos_mgr.get_current_type() != gizmo_type) + gizmos_mgr.open_gizmo(gizmo_type); + } else + wxGetApp().plater()->toggle_layers_editing(true); } } else { @@ -2266,16 +2269,30 @@ void ObjectList::update_info_items(size_t obj_idx) wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx); assert(item_obj.IsOk()); - for (InfoItemType type : {InfoItemType::CustomSupports, InfoItemType::CustomSeam}) { + for (InfoItemType type : {InfoItemType::CustomSupports, + InfoItemType::CustomSeam, + InfoItemType::VariableLayerHeight}) { wxDataViewItem item = m_objects_model->GetInfoItemByType(item_obj, type); bool shows = item.IsOk(); - bool should_show = printer_technology() == ptFFF - && std::any_of(model_object->volumes.begin(), model_object->volumes.end(), - [type](const ModelVolume* mv) { - return ! (type == InfoItemType::CustomSupports - ? mv->supported_facets.empty() - : mv->seam_facets.empty()); - }); + bool should_show = false; + + switch (type) { + case InfoItemType::CustomSupports : + case InfoItemType::CustomSeam : + should_show = printer_technology() == ptFFF + && std::any_of(model_object->volumes.begin(), model_object->volumes.end(), + [type](const ModelVolume* mv) { + return ! (type == InfoItemType::CustomSupports + ? mv->supported_facets.empty() + : mv->seam_facets.empty()); + }); + break; + + case InfoItemType::VariableLayerHeight : + should_show = printer_technology() == ptFFF + && ! model_object->layer_height_profile.empty(); + break; + } if (! shows && should_show) { m_objects_model->AddInfoChild(item_obj, type); diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 541c2f8a9e6..49c75f9f2f6 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -54,9 +54,9 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_type(itInfo), m_extruder(wxEmptyString) { - m_name = info_type == InfoItemType::CustomSupports - ? _L("Paint-on supports") - : _L("Paint-on seam"); + m_name = info_type == InfoItemType::CustomSupports ? _L("Paint-on supports") + : info_type == InfoItemType::CustomSeam ? _L("Paint-on seam") + : _L("Variable layer height"); m_info_item_type = info_type; } diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index cf440f5dc4f..1dd41bb1079 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -49,7 +49,8 @@ enum class InfoItemType { Undef, CustomSupports, - CustomSeam + CustomSeam, + VariableLayerHeight }; class ObjectDataViewModelNode; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 50adb89e418..5db983f43a9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4967,6 +4967,12 @@ void Plater::convert_unit(ConversionType conv_type) } } +void Plater::toggle_layers_editing(bool enable) +{ + if (canvas3D()->is_layers_editing_enabled() != enable) + wxPostEvent(canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); +} + void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) { wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 16590ed9b1a..e99feee82f7 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -191,6 +191,7 @@ public: bool is_selection_empty() const; void scale_selection_to_fit_print_volume(); void convert_unit(ConversionType conv_type); + void toggle_layers_editing(bool enable); void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); From 8af953c9d2d3f4a507691245324167b28a086b3d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 20 Apr 2021 08:53:56 +0200 Subject: [PATCH 091/154] Removed mutable members from class Bed3D --- src/slic3r/GUI/3DBed.cpp | 83 +++++++++++++++++++++------------------- src/slic3r/GUI/3DBed.hpp | 24 ++++++------ 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 74b58b052af..1c43e7eb044 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -113,7 +113,7 @@ void Bed3D::Axes::render() const glsafe(::glPopMatrix()); }; - m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); + const_cast(&m_arrow)->init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) @@ -143,13 +143,6 @@ void Bed3D::Axes::render() const glsafe(::glDisable(GL_DEPTH_TEST)); } -Bed3D::Bed3D() - : m_type(Custom) - , m_vbo_id(0) - , m_scale_factor(1.0f) -{ -} - bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { auto check_texture = [](const std::string& texture) { @@ -193,7 +186,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c ExPolygon poly; for (const Vec2d& p : m_shape) { - poly.contour.append(Point(scale_(p(0)), scale_(p(1)))); + poly.contour.append({ scale_(p(0)), scale_(p(1)) }); } calc_triangles(poly); @@ -228,7 +221,8 @@ Point Bed3D::point_projection(const Point& point) const void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture) const { - m_scale_factor = scale_factor; + float* factor = const_cast(&m_scale_factor); + *factor = scale_factor; if (show_axes) render_axes(); @@ -247,22 +241,24 @@ void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, void Bed3D::calc_bounding_boxes() const { - m_bounding_box = BoundingBoxf3(); + BoundingBoxf3* bounding_box = const_cast(&m_bounding_box); + *bounding_box = BoundingBoxf3(); for (const Vec2d& p : m_shape) { - m_bounding_box.merge(Vec3d(p(0), p(1), 0.0)); + bounding_box->merge({ p(0), p(1), 0.0 }); } - m_extended_bounding_box = m_bounding_box; + BoundingBoxf3* extended_bounding_box = const_cast(&m_extended_bounding_box); + *extended_bounding_box = m_bounding_box; // extend to contain axes - m_extended_bounding_box.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); - m_extended_bounding_box.merge(m_extended_bounding_box.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, m_extended_bounding_box.max(2))); + extended_bounding_box->merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); + extended_bounding_box->merge(extended_bounding_box->min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, extended_bounding_box->max(2))); // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); if (model_bb.defined) { model_bb.translate(m_model_offset); - m_extended_bounding_box.merge(model_bb); + extended_bounding_box->merge(model_bb); } } @@ -339,21 +335,24 @@ void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) co void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const { + GLTexture* texture = const_cast(&m_texture); + GLTexture* temp_texture = const_cast(&m_temp_texture); + if (m_texture_filename.empty()) { - m_texture.reset(); + texture->reset(); render_default(bottom); return; } - if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename)) { - m_texture.reset(); + if (texture->get_id() == 0 || texture->get_source() != m_texture_filename) { + texture->reset(); if (boost::algorithm::iends_with(m_texture_filename, ".svg")) { // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) { + if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) { + if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) { render_default(bottom); return; } @@ -361,15 +360,15 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) { + if (!texture->load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) { render_default(bottom); return; } } else if (boost::algorithm::iends_with(m_texture_filename, ".png")) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) { - if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) { + if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) { + if (!temp_texture->load_from_file(m_texture_filename, false, GLTexture::None, false)) { render_default(bottom); return; } @@ -377,7 +376,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) { + if (!texture->load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) { render_default(bottom); return; } @@ -387,13 +386,13 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const return; } } - else if (m_texture.unsent_compressed_data_available()) { + else if (texture->unsent_compressed_data_available()) { // sends to gpu the already available compressed levels of the main texture - m_texture.send_compressed_data_to_gpu(); + texture->send_compressed_data_to_gpu(); // the temporary texture is not needed anymore, reset it - if (m_temp_texture.get_id() != 0) - m_temp_texture.reset(); + if (temp_texture->get_id() != 0) + temp_texture->reset(); canvas.request_extra_frame(); @@ -406,9 +405,11 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const shader->set_uniform("transparent_background", bottom); shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); - if (m_vbo_id == 0) { - glsafe(::glGenBuffers(1, &m_vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + unsigned int* vbo_id = const_cast(&m_vbo_id); + + if (*vbo_id == 0) { + glsafe(::glGenBuffers(1, vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } @@ -428,12 +429,12 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); // show the temporary texture while no compressed data is available - GLuint tex_id = (GLuint)m_temp_texture.get_id(); + GLuint tex_id = (GLuint)temp_texture->get_id(); if (tex_id == 0) - tex_id = (GLuint)m_texture.get_id(); + tex_id = (GLuint)texture->get_id(); glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id)); if (position_id != -1) { glsafe(::glEnableVertexAttribArray(position_id)); @@ -471,24 +472,26 @@ void Bed3D::render_model() const if (m_model_filename.empty()) return; - if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename)) { + GLModel* model = const_cast(&m_model); + + if (model->get_filename() != m_model_filename && model->init_from_file(m_model_filename)) { // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad Vec3d shift = m_bounding_box.center(); shift(2) = -0.03; - m_model_offset = shift; + *const_cast(&m_model_offset) = shift; // update extended bounding box calc_bounding_boxes(); } - if (!m_model.get_filename().empty()) { + if (!model->get_filename().empty()) { GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); shader->set_uniform("uniform_color", m_model_color); ::glPushMatrix(); ::glTranslated(m_model_offset(0), m_model_offset(1), m_model_offset(2)); - m_model.render(); + model->render(); ::glPopMatrix(); shader->stop_using(); } @@ -511,7 +514,7 @@ void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) co void Bed3D::render_default(bool bottom) const { - m_texture.reset(); + const_cast(&m_texture)->reset(); unsigned int triangles_vcount = m_triangles.get_vertices_count(); if (triangles_vcount > 0) { diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 6266304bee7..c2630b79931 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -48,7 +48,7 @@ class Bed3D private: Vec3d m_origin{ Vec3d::Zero() }; float m_stem_length{ DefaultStemLength }; - mutable GLModel m_arrow; + GLModel m_arrow; public: const Vec3d& get_origin() const { return m_origin; } @@ -67,28 +67,28 @@ public: }; private: - EType m_type; + EType m_type{ Custom }; Pointfs m_shape; std::string m_texture_filename; std::string m_model_filename; - mutable BoundingBoxf3 m_bounding_box; - mutable BoundingBoxf3 m_extended_bounding_box; + BoundingBoxf3 m_bounding_box; + BoundingBoxf3 m_extended_bounding_box; Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; - mutable GLTexture m_texture; - mutable GLModel m_model; - mutable Vec3d m_model_offset{ Vec3d::Zero() }; - std::array m_model_color{ 0.235f, 0.235f, 0.235f, 1.0f }; + GLTexture m_texture; // temporary texture shown until the main texture has still no levels compressed - mutable GLTexture m_temp_texture; - mutable unsigned int m_vbo_id; + GLTexture m_temp_texture; + GLModel m_model; + Vec3d m_model_offset{ Vec3d::Zero() }; + std::array m_model_color{ 0.235f, 0.235f, 0.235f, 1.0f }; + unsigned int m_vbo_id{ 0 }; Axes m_axes; - mutable float m_scale_factor; + float m_scale_factor{ 1.0f }; public: - Bed3D(); + Bed3D() = default; ~Bed3D() { reset(); } EType get_type() const { return m_type; } From caa2d9663ba6d274cd72812b1db10b5bbf1608ab Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 22 Mar 2021 22:36:09 +0100 Subject: [PATCH 092/154] fix of notification states and upload progress bar notification fadeout --- src/slic3r/GUI/NotificationManager.cpp | 33 ++++++++++++++++++-------- src/slic3r/GUI/NotificationManager.hpp | 3 +++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index e83a0014c4a..533bcb8dc68 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -169,13 +169,16 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); - // find if hovered - if (m_state == EState::Hovered) - m_state = EState::Shown; - + + // find if hovered FIXME: do it only in update state? + if (m_state == EState::Hovered) { + m_state = EState::Unknown; + init(); + } + if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { ImGui::SetNextWindowFocus(); - m_state = EState::Hovered; + set_hovered(); } // color change based on fading out @@ -300,8 +303,8 @@ void NotificationManager::PopNotification::init() if (m_lines_count == 3) m_multiline = true; m_notification_start = GLCanvas3D::timestamp_now(); - //if (m_state != EState::Hidden) - // m_state = EState::Shown; + if (m_state == EState::Unknown) + m_state = EState::Shown; } void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) { @@ -579,9 +582,10 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 // reset timers - hovered state is set in render if (m_state == EState::Hovered) { m_current_fade_opacity = 1.0f; - m_notification_start = now; + m_state = EState::Unknown; + init(); // Timers when not fading - } else if (m_state != EState::FadingOut && m_data.duration != 0 && !paused) { + } else if (m_state != EState::NotFading && m_state != EState::FadingOut && m_data.duration != 0 && !paused) { int64_t up_time = now - m_notification_start; if (up_time >= m_data.duration * 1000) { m_state = EState::FadingOut; @@ -787,6 +791,8 @@ void NotificationManager::ProgressBarNotification::init() PopNotification::init(); m_lines_count++; m_endlines.push_back(m_endlines.back()); + if(m_state == EState::Shown) + m_state = EState::NotFading; } void NotificationManager::ProgressBarNotification::count_spaces() { @@ -826,12 +832,19 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); } //------PrintHostUploadNotification---------------- +void NotificationManager::PrintHostUploadNotification::init() +{ + ProgressBarNotification::init(); + if (m_state == EState::NotFading && m_uj_state == UploadJobState::PB_COMPLETED) + m_state = EState::Shown; +} void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) { m_uj_state = UploadJobState::PB_COMPLETED; m_has_cancel_button = false; + init(); } else if (percent < 0.0f) { error(); } else { @@ -1123,7 +1136,7 @@ void NotificationManager::push_exporting_finished_notification(const std::string void NotificationManager::push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage) { std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); - NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 0, text }; + NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 10, text }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0, id, filesize), 0); } void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 4f3900aeb46..4224694c4d9 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -207,6 +207,7 @@ private: Unknown, // NOT initialized Hidden, Shown, // Requesting Render at some time if duration != 0 + NotFading, // Never jumps to state Fading out even if duration says so FadingOut, // Requesting Render at some time ClosePending, // Requesting Render Finished, // Requesting Render @@ -237,6 +238,7 @@ private: int64_t next_render() const { return is_finished() ? 0 : m_next_render; } EState get_state() const { return m_state; } bool is_hovered() const { return m_state == EState::Hovered; } + void set_hovered() { if (m_state != EState::Finished || m_state != EState::ClosePending || m_state != EState::Hidden || m_state != EState::Unknown) m_state = EState::Hovered; } // Call after every size change virtual void init(); @@ -401,6 +403,7 @@ private: { m_has_cancel_button = true; } + virtual void init() override; static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return "[" + std::to_string(id) + "] " + filename + " -> " + host; } virtual void set_percentage(float percent) override; void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } From 53ee91dcf93aa09f615a1a135b41af19a6b3f0b5 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 23 Mar 2021 09:43:09 +0100 Subject: [PATCH 093/154] typo fix --- src/slic3r/GUI/NotificationManager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 4224694c4d9..95532c94e41 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -238,7 +238,7 @@ private: int64_t next_render() const { return is_finished() ? 0 : m_next_render; } EState get_state() const { return m_state; } bool is_hovered() const { return m_state == EState::Hovered; } - void set_hovered() { if (m_state != EState::Finished || m_state != EState::ClosePending || m_state != EState::Hidden || m_state != EState::Unknown) m_state = EState::Hovered; } + void set_hovered() { if (m_state != EState::Finished && m_state != EState::ClosePending && m_state != EState::Hidden && m_state != EState::Unknown) m_state = EState::Hovered; } // Call after every size change virtual void init(); From ad300cde1a1c5826f7b7126f617750fe7501b290 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 23 Mar 2021 14:46:55 +0100 Subject: [PATCH 094/154] Upload notification text fix --- src/slic3r/GUI/NotificationManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 533bcb8dc68..6c5918fdef0 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -859,7 +859,7 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_PROGRESS: { ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - float uploaded = m_file_size / 100 * m_percentage; + float uploaded = m_file_size * m_percentage; std::stringstream stream; stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; text = stream.str(); From 34d26eaa3188aea9efd30b805abb3587bed59a9c Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 26 Mar 2021 09:18:07 +0100 Subject: [PATCH 095/154] Changed ToolpathOuside error notification from plater to slicing error notification type so it is grayed out correctly --- src/slic3r/GUI/GLCanvas3D.cpp | 40 ++++++++++++++++++-------- src/slic3r/GUI/NotificationManager.cpp | 8 ++++++ src/slic3r/GUI/NotificationManager.hpp | 1 + 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2149f21e7c9..fa3d96420b8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6314,31 +6314,47 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col #if ENABLE_WARNING_TEXTURE_REMOVAL void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) { + enum ErrorType{ + PLATER_WARNING, + PLATER_ERROR, + SLICING_ERROR + }; std::string text; - bool error = false; + ErrorType error = ErrorType::PLATER_WARNING; switch (warning) { case EWarning::ObjectOutside: text = _u8L("An object outside the print area was detected."); break; - case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = true; break; - case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = true; break; + case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = ErrorType::SLICING_ERROR; break; + case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break; case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible."); break; case EWarning::ObjectClashed: text = _u8L("An object outside the print area was detected.\n" "Resolve the current problem to continue slicing."); - error = true; + error = ErrorType::PLATER_ERROR; break; } auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); - if (state) { - if (error) - notification_manager.push_plater_error_notification(text); - else + switch (error) + { + case PLATER_WARNING: + if (state) notification_manager.push_plater_warning_notification(text); - } - else { - if (error) - notification_manager.close_plater_error_notification(text); else notification_manager.close_plater_warning_notification(text); + break; + case PLATER_ERROR: + if (state) + notification_manager.push_plater_error_notification(text); + else + notification_manager.close_plater_error_notification(text); + break; + case SLICING_ERROR: + if (state) + notification_manager.push_slicing_error_notification(text); + else + notification_manager.close_slicing_error_notification(text); + break; + default: + break; } } #else diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 6c5918fdef0..144c89a1ecd 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1072,6 +1072,14 @@ void NotificationManager::close_slicing_errors_and_warnings() } } } +void NotificationManager::close_slicing_error_notification(const std::string& text) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingError && notification->compare_text(_u8L("ERROR:") + "\n" + text)) { + notification->close(); + } + } +} void NotificationManager::push_slicing_complete_notification(int timestamp, bool large) { std::string hypertext; diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 95532c94e41..74ebedde2c9 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -125,6 +125,7 @@ public: // void set_slicing_warning_gray(const std::string& text, bool g); // immediately stops showing slicing errors void close_slicing_errors_and_warnings(); + void close_slicing_error_notification(const std::string& text); // Release those slicing warnings, which refer to an ObjectID, which is not in the list. // living_oids is expected to be sorted. void remove_slicing_warnings_of_released_objects(const std::vector& living_oids); From 40dc0a883346dbccd2a5b14026438e3e3abfa29e Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 29 Mar 2021 18:25:44 +0200 Subject: [PATCH 096/154] two line text for upload progress bar notification --- src/slic3r/GUI/NotificationManager.cpp | 56 ++++++++++++++++++-------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 144c89a1ecd..980927d9879 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -327,8 +327,8 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons if (m_multiline) { int last_end = 0; - float starting_y = m_line_height/2;//10; - float shift_y = m_line_height;// -m_line_height / 20; + float starting_y = m_line_height/2; + float shift_y = m_line_height; for (size_t i = 0; i < m_lines_count; i++) { std::string line = m_text1.substr(last_end , m_endlines[i] - last_end); if(i < m_lines_count - 1) @@ -789,8 +789,16 @@ bool NotificationManager::ExportFinishedNotification::on_text_click() void NotificationManager::ProgressBarNotification::init() { PopNotification::init(); - m_lines_count++; - m_endlines.push_back(m_endlines.back()); + //m_lines_count++; + if(m_lines_count >= 2) { + m_lines_count = 3; + m_multiline = true; + while (m_endlines.size() < 3) + m_endlines.push_back(m_endlines.back()); + } else { + m_lines_count = 2; + m_endlines.push_back(m_endlines.back()); + } if(m_state == EState::Shown) m_state = EState::NotFading; } @@ -813,20 +821,36 @@ void NotificationManager::ProgressBarNotification::count_spaces() void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { // line1 - we do not print any more text than what fits on line 1. Line 2 is bar. - ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 - win_size_y / 6 - m_line_height / 2); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); - if (m_has_cancel_button) - render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + if (m_multiline) { + // two lines text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height / 4); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height + m_line_height / 4); + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + imgui.text(line.c_str()); + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } else { + //one line text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(/*win_size_y / 2 - win_size_y / 6 -*/ m_line_height / 4); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } + } void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); - ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + m_line_height / 4); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + m_line_height / 4); + ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); + ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); ImVec2 midPoint = ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); @@ -864,23 +888,23 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; text = stream.str(); ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 /*- m_line_height / 4 * 3*/); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); break; } case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_ERROR: text = _u8L("ERROR"); ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); break; case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_CANCELLED: text = _u8L("CANCELED"); ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); break; case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED: text = _u8L("COMPLETED"); ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); break; } From ad406bd820d5ac7bebac74f90d801e981e49090c Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 30 Mar 2021 21:03:59 +0200 Subject: [PATCH 097/154] compare upload notification by id and not show id in text --- src/slic3r/GUI/NotificationManager.cpp | 44 +++++++++++++++++--------- src/slic3r/GUI/NotificationManager.hpp | 6 ++-- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 980927d9879..005b3c11f14 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -552,7 +552,7 @@ void NotificationManager::PopNotification::update(const NotificationData& n) m_text2 = n.text2; init(); } -bool NotificationManager::PopNotification::compare_text(const std::string& text) +bool NotificationManager::PopNotification::compare_text(const std::string& text) const { std::wstring wt1 = boost::nowide::widen(m_text1); std::wstring wt2 = boost::nowide::widen(text); @@ -1167,39 +1167,53 @@ void NotificationManager::push_exporting_finished_notification(const std::string void NotificationManager::push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage) { + // find if upload with same id was not already in notification + // done by compare_jon_id not compare_text thus has to be performed here + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload && dynamic_cast(notification.get())->compare_job_id(id)) { + return; + } + } std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 10, text }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0, id, filesize), 0); } void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) { - std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); for (std::unique_ptr& notification : m_pop_notifications) { - if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { - dynamic_cast(notification.get())->set_percentage(percentage); - wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if (phun->compare_job_id(id)) { + phun->set_percentage(percentage); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } } } } void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host) { - std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); for (std::unique_ptr& notification : m_pop_notifications) { - if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { - dynamic_cast(notification.get())->cancel(); - wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); - break; + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if (phun->compare_job_id(id)) { + phun->cancel(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } } } } void NotificationManager::upload_job_notification_show_error(int id, const std::string& filename, const std::string& host) { - std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); for (std::unique_ptr& notification : m_pop_notifications) { - if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { - dynamic_cast(notification.get())->error(); - wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); - break; + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if(phun->compare_job_id(id)) { + phun->error(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } } } } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 74ebedde2c9..1cf7809883f 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -232,7 +232,7 @@ private: const NotificationData get_data() const { return m_data; } const bool is_gray() const { return m_is_gray; } void set_gray(bool g) { m_is_gray = g; } - bool compare_text(const std::string& text); + virtual bool compare_text(const std::string& text) const; void hide(bool h) { if (is_finished()) return; m_state = h ? EState::Hidden : EState::Unknown; } // sets m_next_render with time of next mandatory rendering. Delta is time since last render. bool update_state(bool paused, const int64_t delta); @@ -405,10 +405,12 @@ private: m_has_cancel_button = true; } virtual void init() override; - static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return "[" + std::to_string(id) + "] " + filename + " -> " + host; } + static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; } virtual void set_percentage(float percent) override; void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; } + bool compare_job_id(const int other_id) const { return m_job_id == other_id; } + virtual bool compare_text(const std::string& text) const override { return false; } protected: virtual void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, From 640b7879fb84c5dd76d2e90b6d73df218d6de204 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 11 Apr 2021 11:47:20 +0200 Subject: [PATCH 098/154] Error appearance of upload notification and dividing lines with lesser impotance of spaces --- src/slic3r/GUI/NotificationManager.cpp | 185 ++++++++++++++++++------- src/slic3r/GUI/NotificationManager.hpp | 27 ++-- 2 files changed, 157 insertions(+), 55 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 005b3c11f14..7f61ad7f393 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -188,22 +188,8 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init fading_pop = true; } - // background color - if (m_is_gray) { - ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); - } - else if (m_data.level == NotificationLevel::ErrorNotification) { - ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); - backcolor.x += 0.3f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); - } - else if (m_data.level == NotificationLevel::WarningNotification) { - ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); - backcolor.x += 0.3f; - backcolor.y += 0.15f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); - } + bool bgrnd_color_pop = push_background_color(); + // name of window indentifies window - has to be unique string if (m_id == 0) @@ -222,13 +208,34 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init } imgui.end(); - if (m_is_gray || m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) + if (bgrnd_color_pop) ImGui::PopStyleColor(); if (fading_pop) ImGui::PopStyleColor(2); } - +bool NotificationManager::PopNotification::push_background_color() +{ + if (m_is_gray) { + ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; + } + if (m_data.level == NotificationLevel::ErrorNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; + } + if (m_data.level == NotificationLevel::WarningNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + backcolor.y += 0.15f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; + } + return false; +} void NotificationManager::PopNotification::count_spaces() { //determine line width @@ -245,34 +252,28 @@ void NotificationManager::PopNotification::count_spaces() m_window_width = m_line_height * 25; } -void NotificationManager::PopNotification::init() +void NotificationManager::PopNotification::count_lines() { - // Do not init closing notification - if (is_finished()) - return; - - std::string text = m_text1 + " " + m_hypertext; - size_t last_end = 0; - m_lines_count = 0; + std::string text = m_text1 + " " + m_hypertext; + size_t last_end = 0; + m_lines_count = 0; - count_spaces(); - - // count lines m_endlines.clear(); while (last_end < text.length() - 1) { - size_t next_hard_end = text.find_first_of('\n', last_end); - if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { + size_t next_hard_end = text.find_first_of('\n', last_end); + if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { //next line is ended by '/n' m_endlines.push_back(next_hard_end); last_end = next_hard_end + 1; - } else { + } + else { // find next suitable endline if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) { // more than one line till end - size_t next_space = text.find_first_of(' ', last_end); + size_t next_space = text.find_first_of(' ', last_end); if (next_space > 0) { - size_t next_space_candidate = text.find_first_of(' ', next_space + 1); + size_t next_space_candidate = text.find_first_of(' ', next_space + 1); while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { next_space = next_space_candidate; next_space_candidate = text.find_first_of(' ', next_space + 1); @@ -286,7 +287,8 @@ void NotificationManager::PopNotification::init() } m_endlines.push_back(last_end + letter_count); last_end += letter_count; - } else { + } + else { m_endlines.push_back(next_space); last_end = next_space + 1; } @@ -300,6 +302,17 @@ void NotificationManager::PopNotification::init() } m_lines_count++; } +} + +void NotificationManager::PopNotification::init() +{ + // Do not init closing notification + if (is_finished()) + return; + + count_spaces(); + count_lines(); + if (m_lines_count == 3) m_multiline = true; m_notification_start = GLCanvas3D::timestamp_now(); @@ -802,22 +815,64 @@ void NotificationManager::ProgressBarNotification::init() if(m_state == EState::Shown) m_state = EState::NotFading; } -void NotificationManager::ProgressBarNotification::count_spaces() -{ - //determine line width - m_line_height = ImGui::CalcTextSize("A").y; - m_left_indentation = m_line_height; - if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { - std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); - float picture_width = ImGui::CalcTextSize(text.c_str()).x; - m_left_indentation = picture_width + m_line_height / 2; + +void NotificationManager::ProgressBarNotification::count_lines() +{ + std::string text = m_text1 + " " + m_hypertext; + size_t last_end = 0; + m_lines_count = 0; + + m_endlines.clear(); + while (last_end < text.length() - 1) + { + size_t next_hard_end = text.find_first_of('\n', last_end); + if (next_hard_end != std::string::npos && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { + //next line is ended by '/n' + m_endlines.push_back(next_hard_end); + last_end = next_hard_end + 1; + } + else { + // find next suitable endline + if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) { + // more than one line till end + size_t next_space = text.find_first_of(' ', last_end); + if (next_space > 0) { + size_t next_space_candidate = text.find_first_of(' ', next_space + 1); + while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { + next_space = next_space_candidate; + next_space_candidate = text.find_first_of(' ', next_space + 1); + } + // when one word longer than line. Or the last space is too early. + if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset || + ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 4 * 3 + ) { + float width_of_a = ImGui::CalcTextSize("a").x; + int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); + while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { + letter_count++; + } + m_endlines.push_back(last_end + letter_count); + last_end += letter_count; + } + else { + m_endlines.push_back(next_space); + last_end = next_space + 1; + } + } + } + else { + m_endlines.push_back(text.length()); + last_end = text.length(); + } + + } + m_lines_count++; } - m_window_width_offset = m_line_height * (m_has_cancel_button ? 6 : 4); - m_window_width = m_line_height * 25; } + + void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { // line1 - we do not print any more text than what fits on line 1. Line 2 is bar. @@ -862,6 +917,32 @@ void NotificationManager::PrintHostUploadNotification::init() if (m_state == EState::NotFading && m_uj_state == UploadJobState::PB_COMPLETED) m_state = EState::Shown; } +void NotificationManager::PrintHostUploadNotification::count_spaces() +{ + //determine line width + m_line_height = ImGui::CalcTextSize("A").y; + + m_left_indentation = m_line_height; + if (m_uj_state == UploadJobState::PB_ERROR) { + std::string text; + text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + float picture_width = ImGui::CalcTextSize(text.c_str()).x; + m_left_indentation = picture_width + m_line_height / 2; + } + m_window_width_offset = m_line_height * 6; //(m_has_cancel_button ? 6 : 4); + m_window_width = m_line_height * 25; +} +bool NotificationManager::PrintHostUploadNotification::push_background_color() +{ + + if (m_uj_state == UploadJobState::PB_ERROR) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; + } + return false; +} void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) { m_percentage = percent; @@ -911,6 +992,16 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui.text(text.c_str()); } +void NotificationManager::PrintHostUploadNotification::render_left_sign(ImGuiWrapper& imgui) +{ + if (m_uj_state == UploadJobState::PB_ERROR) { + std::string text; + text = ImGui::ErrorMarker; + ImGui::SetCursorPosX(m_line_height / 3); + ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); + imgui.text(text.c_str()); + } +} void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 1cf7809883f..17657948e6b 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -240,11 +240,9 @@ private: EState get_state() const { return m_state; } bool is_hovered() const { return m_state == EState::Hovered; } void set_hovered() { if (m_state != EState::Finished && m_state != EState::ClosePending && m_state != EState::Hidden && m_state != EState::Unknown) m_state = EState::Hovered; } - + protected: // Call after every size change virtual void init(); - // Part of init() - virtual void count_spaces(); // Calculetes correct size but not se it in imgui! virtual void set_next_window_size(ImGuiWrapper& imgui); virtual void render_text(ImGuiWrapper& imgui, @@ -258,13 +256,20 @@ private: const std::string text, bool more = false); // Left sign could be error or warning sign - void render_left_sign(ImGuiWrapper& imgui); + virtual void render_left_sign(ImGuiWrapper& imgui); virtual void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y); // Hypertext action, returns true if notification should close. // Action is stored in NotificationData::callback as std::function virtual bool on_text_click(); - protected: + + // Part of init(), counts horizontal spacing like left indentation + virtual void count_spaces(); + // Part of init(), counts end lines + virtual void count_lines(); + // returns true if PopStyleColor should be called later to pop this push + virtual bool push_background_color(); + const NotificationData m_data; // For reusing ImGUI windows. NotificationIDProvider &m_id_provider; @@ -367,7 +372,8 @@ private: virtual void set_percentage(float percent) { m_percentage = percent; } protected: virtual void init() override; - virtual void count_spaces() override; + virtual void count_lines() override; + virtual void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; @@ -378,6 +384,8 @@ private: const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) {} + virtual void render_minimize_button(ImGuiWrapper& imgui, + const float win_pos_x, const float win_pos_y) override {} float m_percentage; bool m_has_cancel_button {false}; @@ -404,20 +412,23 @@ private: { m_has_cancel_button = true; } - virtual void init() override; static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; } virtual void set_percentage(float percent) override; void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } - void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; } + void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; init(); } bool compare_job_id(const int other_id) const { return m_job_id == other_id; } virtual bool compare_text(const std::string& text) const override { return false; } protected: + virtual void init() override; + virtual void count_spaces() override; + virtual bool push_background_color() override; virtual void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; virtual void render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; + virtual void render_left_sign(ImGuiWrapper& imgui) override; // Identifies job in cancel callback int m_job_id; // Size of uploaded size to be displayed in MB From 51a8af03fde83f5c4f0cb367c976dbc2ed3cc27b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 13 Apr 2021 08:39:07 +0200 Subject: [PATCH 099/154] Check of correct suffix during PrintHostSend dialog. --- src/slic3r/GUI/PrintHostDialogs.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index f69db2ea307..762450c5374 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -68,8 +68,10 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr combo_groups->SetValue(recent_group); } - btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); - + auto* szr = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + auto* btn_ok = szr->GetAffirmativeButton(); + btn_sizer->Add(szr); + wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH)); if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') { recent_path += '/'; @@ -82,6 +84,20 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr txt_filename->SetValue(recent_path); txt_filename->SetFocus(); + wxString suffix = recent_path.substr(recent_path.find_last_of('.')); + + btn_ok->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) { + wxString path = txt_filename->GetValue(); + // .gcode suffix control + if (!path.Lower().EndsWith(suffix.Lower())) + { + wxMessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO); + if (msg_wingow.ShowModal() == wxID_NO) + return; + } + EndDialog(wxID_OK); + }); + Fit(); CenterOnParent(); From 08a9d6c559d68c99192f76b6fde0375e319d5c47 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 20 Apr 2021 11:42:34 +0200 Subject: [PATCH 100/154] Removed mutable members from class OpenGLManager::GLInfo --- src/slic3r/GUI/OpenGLManager.cpp | 36 +++++++++++++++----------------- src/slic3r/GUI/OpenGLManager.hpp | 14 ++++++------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 0b246171721..91f1f1f0b6b 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -90,22 +90,24 @@ float OpenGLManager::GLInfo::get_max_anisotropy() const void OpenGLManager::GLInfo::detect() const { - m_version = gl_get_string_safe(GL_VERSION, "N/A"); - m_glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A"); - m_vendor = gl_get_string_safe(GL_VENDOR, "N/A"); - m_renderer = gl_get_string_safe(GL_RENDERER, "N/A"); + *const_cast(&m_version) = gl_get_string_safe(GL_VERSION, "N/A"); + *const_cast(&m_glsl_version) = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A"); + *const_cast(&m_vendor) = gl_get_string_safe(GL_VENDOR, "N/A"); + *const_cast(&m_renderer) = gl_get_string_safe(GL_RENDERER, "N/A"); - glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size)); + int* max_tex_size = const_cast(&m_max_tex_size); + glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, max_tex_size)); - m_max_tex_size /= 2; + *max_tex_size /= 2; if (Slic3r::total_physical_memory() / (1024 * 1024 * 1024) < 6) - m_max_tex_size /= 2; + *max_tex_size /= 2; - if (GLEW_EXT_texture_filter_anisotropic) - glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy)); - - m_detected = true; + if (GLEW_EXT_texture_filter_anisotropic) { + float* max_anisotropy = const_cast(&m_max_anisotropy); + glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); + } + *const_cast(&m_detected) = true; } static bool version_greater_or_equal_to(const std::string& version, unsigned int major, unsigned int minor) @@ -174,19 +176,16 @@ std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extension out << b_start << "Renderer: " << b_end << m_renderer << line_end; out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; - if (extensions) - { + if (extensions) { std::vector extensions_list; std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, ""); boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off); - if (!extensions_list.empty()) - { + if (!extensions_list.empty()) { out << h2_start << "Installed extensions:" << h2_end << line_end; std::sort(extensions_list.begin(), extensions_list.end()); - for (const std::string& ext : extensions_list) - { + for (const std::string& ext : extensions_list) { out << ext << line_end; } } @@ -304,8 +303,7 @@ wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent) 0 }; - if (s_multisample == EMultisampleState::Unknown) - { + if (s_multisample == EMultisampleState::Unknown) { detect_multisample(attribList); // // debug output // std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index 5f8cd7959c5..ca9452db664 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -22,14 +22,14 @@ public: class GLInfo { - mutable bool m_detected{ false }; - mutable int m_max_tex_size{ 0 }; - mutable float m_max_anisotropy{ 0.0f }; + bool m_detected{ false }; + int m_max_tex_size{ 0 }; + float m_max_anisotropy{ 0.0f }; - mutable std::string m_version; - mutable std::string m_glsl_version; - mutable std::string m_vendor; - mutable std::string m_renderer; + std::string m_version; + std::string m_glsl_version; + std::string m_vendor; + std::string m_renderer; public: GLInfo() = default; From aad1b3a8f60b36cb4f5aed823c0cbaa8e8f3bb84 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 20 Apr 2021 12:16:55 +0200 Subject: [PATCH 101/154] Removed mutable members from class GLCanvas3D --- src/slic3r/GUI/GLCanvas3D.cpp | 164 +++++++++++++++++----------------- src/slic3r/GUI/GLCanvas3D.hpp | 40 ++++----- 2 files changed, 98 insertions(+), 106 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fa3d96420b8..97038723bbf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3528,7 +3528,7 @@ Vec2d GLCanvas3D::get_local_mouse_position() const void GLCanvas3D::set_tooltip(const std::string& tooltip) const { if (m_canvas != nullptr) - m_tooltip.set_text(tooltip); + const_cast(&m_tooltip)->set_text(tooltip); } void GLCanvas3D::do_move(const std::string& snapshot_type) @@ -3545,22 +3545,19 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) Selection::EMode selection_mode = m_selection.get_mode(); - for (const GLVolume* v : m_volumes.volumes) - { + for (const GLVolume* v : m_volumes.volumes) { int object_idx = v->object_idx(); int instance_idx = v->instance_idx(); int volume_idx = v->volume_idx(); std::pair done_id(object_idx, instance_idx); - if ((0 <= object_idx) && (object_idx < (int)m_model->objects.size())) - { + if (0 <= object_idx && object_idx < (int)m_model->objects.size()) { done.insert(done_id); // Move instances/volumes ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) - { + if (model_object != nullptr) { if (selection_mode == Selection::Instance) model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); else if (selection_mode == Selection::Volume) @@ -3576,8 +3573,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) } // Fixes sinking/flying instances - for (const std::pair& i : done) - { + for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); m_selection.translate(i.first, i.second, shift); @@ -3936,13 +3932,13 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) const em *= m_retina_helper->get_scale_factor(); #endif - if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, m_mouse_wheel)) - m_imgui_undo_redo_hovered_pos = hovered; + int* mouse_wheel = const_cast(&m_mouse_wheel); + if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, *mouse_wheel)) + *const_cast(&m_imgui_undo_redo_hovered_pos) = hovered; else - m_imgui_undo_redo_hovered_pos = -1; + *const_cast(&m_imgui_undo_redo_hovered_pos) = -1; - if (selected >= 0) - { + if (selected >= 0) { is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); action_taken = true; } @@ -3983,9 +3979,10 @@ bool GLCanvas3D::_render_search_list(float pos_x) const char *s = new char[255]; strcpy(s, search_line.empty() ? _u8L("Enter a search term").c_str() : search_line.c_str()); - imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, - sidebar.get_searcher().view_params, - selected, edited, m_mouse_wheel, wxGetApp().is_localized()); + int* mouse_wheel = const_cast(&m_mouse_wheel); + imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, + sidebar.get_searcher().view_params, + selected, edited, *mouse_wheel, wxGetApp().is_localized()); search_line = s; delete [] s; @@ -4844,8 +4841,10 @@ void GLCanvas3D::_refresh_if_shown_on_screen() void GLCanvas3D::_picking_pass() const { + std::vector* hover_volume_idxs = const_cast*>(&m_hover_volume_idxs); + if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) { - m_hover_volume_idxs.clear(); + hover_volume_idxs->clear(); // Render the object for picking. // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. @@ -4860,9 +4859,10 @@ void GLCanvas3D::_picking_pass() const glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - if (m_camera_clipping_plane.is_active()) { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); + ClippingPlane* camera_clipping_plane = const_cast(&m_camera_clipping_plane); + *camera_clipping_plane = m_gizmos.get_clipping_plane(); + if (camera_clipping_plane->is_active()) { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)camera_clipping_plane->get_data()); ::glEnable(GL_CLIP_PLANE0); } _render_volumes_for_picking(); @@ -4888,11 +4888,11 @@ void GLCanvas3D::_picking_pass() const if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { // do not add the volume id if any gizmo is active and CTRL is pressed if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) - m_hover_volume_idxs.emplace_back(volume_id); - m_gizmos.set_hover_id(-1); + hover_volume_idxs->emplace_back(volume_id); + const_cast(&m_gizmos)->set_hover_id(-1); } else - m_gizmos.set_hover_id(inside && (unsigned int)volume_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - volume_id) : -1); + const_cast(&m_gizmos)->set_hover_id(inside && (unsigned int)volume_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - volume_id) : -1); _update_volumes_hover_state(); } @@ -4900,12 +4900,11 @@ void GLCanvas3D::_picking_pass() const void GLCanvas3D::_rectangular_selection_picking_pass() const { - m_gizmos.set_hover_id(-1); + const_cast(&m_gizmos)->set_hover_id(-1); std::set idxs; - if (m_picking_enabled) - { + if (m_picking_enabled) { if (m_multisample_allowed) // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. glsafe(::glDisable(GL_MULTISAMPLE)); @@ -4926,8 +4925,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const int left = (int)m_rectangle_selection.get_left(); int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); - if ((left >= 0) && (top >= 0)) - { + if (left >= 0 && top >= 0) { #define USE_PARALLEL 1 #if USE_PARALLEL struct Pixel @@ -4947,7 +4945,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const for (size_t i = range.begin(); i < range.end(); ++i) if (frame[i].valid()) { int volume_id = frame[i].id(); - if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { mutex.lock(); idxs.insert(volume_id); mutex.unlock(); @@ -4962,14 +4960,14 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const { int px_id = 4 * i; int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16); - if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) idxs.insert(volume_id); } #endif // USE_PARALLEL } } - m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); + const_cast*>(&m_hover_volume_idxs)->assign(idxs.begin(), idxs.end()); _update_volumes_hover_state(); } @@ -5062,7 +5060,9 @@ void GLCanvas3D::_render_objects() const glsafe(::glEnable(GL_DEPTH_TEST)); - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); + ClippingPlane* camera_clipping_plane = const_cast(&m_camera_clipping_plane); + GLVolumeCollection* volumes = const_cast(&m_volumes); + *camera_clipping_plane = m_gizmos.get_clipping_plane(); if (m_picking_enabled) { // Update the layer editing selection to the first object selected, update the current object maximum Z. @@ -5070,17 +5070,17 @@ void GLCanvas3D::_render_objects() const if (m_config != nullptr) { const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); - m_volumes.set_print_box((float)bed_bb.min(0) - BedEpsilon, (float)bed_bb.min(1) - BedEpsilon, 0.0f, (float)bed_bb.max(0) + BedEpsilon, (float)bed_bb.max(1) + BedEpsilon, (float)m_config->opt_float("max_print_height")); - m_volumes.check_outside_state(m_config, nullptr); + volumes->set_print_box((float)bed_bb.min(0) - BedEpsilon, (float)bed_bb.min(1) - BedEpsilon, 0.0f, (float)bed_bb.max(0) + BedEpsilon, (float)bed_bb.max(1) + BedEpsilon, (float)m_config->opt_float("max_print_height")); + volumes->check_outside_state(m_config, nullptr); } } if (m_use_clipping_planes) - m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); + volumes->set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); else - m_volumes.set_z_range(-FLT_MAX, FLT_MAX); + volumes->set_z_range(-FLT_MAX, FLT_MAX); - m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); + volumes->set_clipping_plane(camera_clipping_plane->get_data()); GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); if (shader != nullptr) { @@ -5088,16 +5088,16 @@ void GLCanvas3D::_render_objects() const if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { int object_id = m_layers_editing.last_object_id; - m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { + volumes->render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { // Which volume to paint without the layer height profile shader? return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); }); // Let LayersEditing handle rendering of the active object using the layer height profile shader. - m_layers_editing.render_volumes(*this, this->m_volumes); + m_layers_editing.render_volumes(*this, *volumes); } else { - // do not cull backfaces to show broken geometry, if any - m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + // do not cull backfaces to show broken geometry, if any + volumes->render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); }); } @@ -5115,11 +5115,11 @@ void GLCanvas3D::_render_objects() const } } - m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); + volumes->render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); shader->stop_using(); } - m_camera_clipping_plane = ClippingPlane::ClipsNothing(); + *camera_clipping_plane = ClippingPlane::ClipsNothing(); } void GLCanvas3D::_render_gcode() const @@ -5160,13 +5160,13 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); #if ENABLE_RETINA_GL const float sc = m_retina_helper->get_scale_factor() * scale; - m_main_toolbar.set_scale(sc); - m_undoredo_toolbar.set_scale(sc); + const_cast(&m_main_toolbar)->set_scale(sc); + const_cast(&m_undoredo_toolbar)->set_scale(sc); collapse_toolbar.set_scale(sc); size *= m_retina_helper->get_scale_factor(); #else - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); + const_cast(&m_main_toolbar)->set_icons_size(size); + const_cast(&m_undoredo_toolbar)->set_icons_size(size); collapse_toolbar.set_icons_size(size); #endif // ENABLE_RETINA_GL @@ -5214,13 +5214,13 @@ void GLCanvas3D::_render_overlays() const // to correctly place them #if ENABLE_RETINA_GL const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); - m_main_toolbar.set_scale(scale); - m_undoredo_toolbar.set_scale(scale); + const_cast(&m_main_toolbar)->set_scale(scale); + const_cast(&m_undoredo_toolbar)->set_scale(scale); wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); #else const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); + const_cast(&m_main_toolbar)->set_icons_size(size); + const_cast(&m_undoredo_toolbar)->set_icons_size(size); wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); #endif // ENABLE_RETINA_GL @@ -5295,12 +5295,12 @@ void GLCanvas3D::_render_gizmos_overlay() const #if ENABLE_RETINA_GL // m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); - m_gizmos.set_overlay_scale(scale); //! #ys_FIXME_experiment + const_cast(&m_gizmos)->set_overlay_scale(scale); //! #ys_FIXME_experiment #else // m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor()); // m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f); - const float size = int(GLGizmosManager::Default_Icons_Size*wxGetApp().toolbar_icon_scale()); - m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment + const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); + const_cast(&m_gizmos)->set_overlay_icon_size(size); //! #ys_FIXME_experiment #endif /* __WXMSW__ */ m_gizmos.render_overlay(); @@ -5319,8 +5319,9 @@ void GLCanvas3D::_render_main_toolbar() const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; - m_main_toolbar.set_position(top, left); - m_main_toolbar.render(*this); + GLToolbar* main_toolbar = const_cast(&m_main_toolbar); + main_toolbar->set_position(top, left); + main_toolbar->render(*this); } void GLCanvas3D::_render_undoredo_toolbar() const @@ -5335,8 +5336,10 @@ void GLCanvas3D::_render_undoredo_toolbar() const const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; - m_undoredo_toolbar.set_position(top, left); - m_undoredo_toolbar.render(*this); + + GLToolbar* undoredo_toolbar = const_cast(&m_undoredo_toolbar); + undoredo_toolbar->set_position(top, left); + undoredo_toolbar->render(*this); } void GLCanvas3D::_render_collapse_toolbar() const @@ -5427,20 +5430,21 @@ void GLCanvas3D::_render_sla_slices() const if (!obj->is_step_done(slaposSliceSupports)) continue; - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); + SlaCap* sla_caps = const_cast(m_sla_caps); + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = sla_caps[0].triangles.find(i); + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = sla_caps[1].triangles.find(i); { - if (it_caps_bottom == m_sla_caps[0].triangles.end()) - it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; - if (! m_sla_caps[0].matches(clip_min_z)) { - m_sla_caps[0].z = clip_min_z; + if (it_caps_bottom == sla_caps[0].triangles.end()) + it_caps_bottom = sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; + if (!sla_caps[0].matches(clip_min_z)) { + sla_caps[0].z = clip_min_z; it_caps_bottom->second.object.clear(); it_caps_bottom->second.supports.clear(); } - if (it_caps_top == m_sla_caps[1].triangles.end()) - it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; - if (! m_sla_caps[1].matches(clip_max_z)) { - m_sla_caps[1].z = clip_max_z; + if (it_caps_top == sla_caps[1].triangles.end()) + it_caps_top = sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; + if (!sla_caps[1].matches(clip_max_z)) { + sla_caps[1].z = clip_max_z; it_caps_top->second.object.clear(); it_caps_top->second.supports.clear(); } @@ -5546,7 +5550,7 @@ void GLCanvas3D::_update_volumes_hover_state() const if (alt_pressed && (shift_pressed || ctrl_pressed)) { // illegal combinations of keys - m_hover_volume_idxs.clear(); + const_cast*>(&m_hover_volume_idxs)->clear(); return; } @@ -5570,7 +5574,7 @@ void GLCanvas3D::_update_volumes_hover_state() const if (hover_modifiers_only && !hover_from_single_instance) { // do not allow to select volumes from different instances - m_hover_volume_idxs.clear(); + const_cast*>(&m_hover_volume_idxs)->clear(); return; } @@ -5591,23 +5595,15 @@ void GLCanvas3D::_update_volumes_hover_state() const (deselect && !m_selection.is_single_full_instance() && (volume.object_idx() == m_selection.get_object_idx()) && (volume.instance_idx() == m_selection.get_instance_idx())) ); - if (as_volume) { - if (deselect) - volume.hover = GLVolume::HS_Deselect; - else - volume.hover = GLVolume::HS_Select; - } + if (as_volume) + volume.hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; else { int object_idx = volume.object_idx(); int instance_idx = volume.instance_idx(); for (GLVolume* v : m_volumes.volumes) { - if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) { - if (deselect) - v->hover = GLVolume::HS_Deselect; - else - v->hover = GLVolume::HS_Select; - } + if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) + v->hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f4d862b66c0..9e9a2501e1d 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -449,13 +449,13 @@ private: wxTimer m_timer; LayersEditing m_layers_editing; Mouse m_mouse; - mutable GLGizmosManager m_gizmos; - mutable GLToolbar m_main_toolbar; - mutable GLToolbar m_undoredo_toolbar; + GLGizmosManager m_gizmos; + GLToolbar m_main_toolbar; + GLToolbar m_undoredo_toolbar; ClippingPlane m_clipping_planes[2]; - mutable ClippingPlane m_camera_clipping_plane; + ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; - mutable SlaCap m_sla_caps[2]; + SlaCap m_sla_caps[2]; std::string m_sidebar_field; // when true renders an extra frame by not resetting m_dirty to false // see request_extra_frame() @@ -463,7 +463,7 @@ private: int m_extra_frame_requested_delayed { std::numeric_limits::max() }; bool m_event_handlers_bound{ false }; - mutable GLVolumeCollection m_volumes; + GLVolumeCollection m_volumes; GCodeViewer m_gcode_viewer; RenderTimer m_render_timer; @@ -478,7 +478,6 @@ private: bool m_dirty; bool m_initialized; bool m_apply_zoom_to_volumes_filter; - mutable std::vector m_hover_volume_idxs; bool m_picking_enabled; bool m_moving_enabled; bool m_dynamic_background_enabled; @@ -487,6 +486,7 @@ private: bool m_tab_down; ECursorType m_cursor_type; GLSelectionRectangle m_rectangle_selection; + std::vector m_hover_volume_idxs; // Following variable is obsolete and it should be safe to remove it. // I just don't want to do it now before a release (Lukas Matena 24.3.2019) @@ -504,13 +504,13 @@ private: RenderStats m_render_stats; #endif // ENABLE_RENDER_STATISTICS - mutable int m_imgui_undo_redo_hovered_pos{ -1 }; - mutable int m_mouse_wheel {0}; + int m_imgui_undo_redo_hovered_pos{ -1 }; + int m_mouse_wheel{ 0 }; int m_selected_extruder; Labels m_labels; - mutable Tooltip m_tooltip; - mutable bool m_tooltip_enabled{ true }; + Tooltip m_tooltip; + bool m_tooltip_enabled{ true }; Slope m_slope; ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla, @@ -519,8 +519,7 @@ private: PrinterTechnology current_printer_technology() const; template - static auto & get_arrange_settings(Self *self) - { + static auto & get_arrange_settings(Self *self) { PrinterTechnology ptech = self->current_printer_technology(); auto *ptr = &self->m_arrange_settings_fff; @@ -529,11 +528,10 @@ private: ptr = &self->m_arrange_settings_sla; } else if (ptech == ptFFF) { auto co_opt = self->m_config->template option("complete_objects"); - if (co_opt && co_opt->value) { + if (co_opt && co_opt->value) ptr = &self->m_arrange_settings_fff_seq_print; - } else { + else ptr = &self->m_arrange_settings_fff; - } } return *ptr; @@ -715,10 +713,9 @@ public: double m_rotation = 0.; BoundingBoxf m_bb; friend class GLCanvas3D; - public: - - inline operator bool() const - { + + public: + inline operator bool() const { return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } @@ -763,8 +760,7 @@ public: void use_slope(bool use) { m_slope.use(use); } void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); } - ArrangeSettings get_arrange_settings() const - { + ArrangeSettings get_arrange_settings() const { const ArrangeSettings &settings = get_arrange_settings(this); ArrangeSettings ret = settings; if (&settings == &m_arrange_settings_fff_seq_print) { From 27336d214564b5615fb1ad3efbbaf3e7ba2a53d6 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 20 Apr 2021 12:55:23 +0200 Subject: [PATCH 102/154] Removed mutable members from class GLVolume --- src/slic3r/GUI/3DScene.cpp | 42 ++++++++++++++++++++------------------ src/slic3r/GUI/3DScene.hpp | 12 +++++------ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 6c226cd74d9..ba62576f23d 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -421,20 +421,24 @@ const BoundingBoxf3& GLVolume::transformed_bounding_box() const const BoundingBoxf3& box = bounding_box(); assert(box.defined || box.min(0) >= box.max(0) || box.min(1) >= box.max(1) || box.min(2) >= box.max(2)); - if (m_transformed_bounding_box_dirty) - { - m_transformed_bounding_box = box.transformed(world_matrix()); - m_transformed_bounding_box_dirty = false; + BoundingBoxf3* transformed_bounding_box = const_cast(&m_transformed_bounding_box); + bool* transformed_bounding_box_dirty = const_cast(&m_transformed_bounding_box_dirty); + if (*transformed_bounding_box_dirty) { + *transformed_bounding_box = box.transformed(world_matrix()); + *transformed_bounding_box_dirty = false; } - - return m_transformed_bounding_box; + return *transformed_bounding_box; } const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const { - if (m_transformed_convex_hull_bounding_box_dirty) - m_transformed_convex_hull_bounding_box = this->transformed_convex_hull_bounding_box(world_matrix()); - return m_transformed_convex_hull_bounding_box; + BoundingBoxf3* transformed_convex_hull_bounding_box = const_cast(&m_transformed_convex_hull_bounding_box); + bool* transformed_convex_hull_bounding_box_dirty = const_cast(&m_transformed_convex_hull_bounding_box_dirty); + if (*transformed_convex_hull_bounding_box_dirty) { + *transformed_convex_hull_bounding_box = this->transformed_convex_hull_bounding_box(world_matrix()); + *transformed_convex_hull_bounding_box_dirty = false; + } + return *transformed_convex_hull_bounding_box; } BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const @@ -795,7 +799,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_BLEND)); } -bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) +bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const { if (config == nullptr) return false; @@ -805,7 +809,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M return false; BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Vec3d(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height"))); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0 }, { unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height") }); // Allow the objects to protrude below the print bed print_volume.min(2) = -1e10; print_volume.min(0) -= BedEpsilon; @@ -817,9 +821,8 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M bool contained_min_one = false; - for (GLVolume* volume : this->volumes) - { - if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) + for (GLVolume* volume : this->volumes) { + if (volume == nullptr || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || (volume->composite_id.volume_id < 0 && !volume->shader_outside_printer_detection_enabled)) continue; const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); @@ -832,10 +835,10 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (contained) contained_min_one = true; - if ((state == ModelInstancePVS_Inside) && volume->is_outside) + if (state == ModelInstancePVS_Inside && volume->is_outside) state = ModelInstancePVS_Fully_Outside; - if ((state == ModelInstancePVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb)) + if (state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb)) state = ModelInstancePVS_Partly_Outside; } @@ -845,7 +848,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M return contained_min_one; } -bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) +bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) const { if (config == nullptr) return false; @@ -867,9 +870,8 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, b partlyOut = false; fullyOut = false; - for (GLVolume* volume : this->volumes) - { - if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) + for (GLVolume* volume : this->volumes) { + if (volume == nullptr || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || (volume->composite_id.volume_id < 0 && !volume->shader_outside_printer_detection_enabled)) continue; const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2ae2a36b29a..25c5443cda3 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -267,15 +267,15 @@ private: // Shift in z required by sla supports+pad double m_sla_shift_z; // Bounding box of this volume, in unscaled coordinates. - mutable BoundingBoxf3 m_transformed_bounding_box; + BoundingBoxf3 m_transformed_bounding_box; // Whether or not is needed to recalculate the transformed bounding box. - mutable bool m_transformed_bounding_box_dirty; + bool m_transformed_bounding_box_dirty; // Convex hull of the volume, if any. std::shared_ptr m_convex_hull; // Bounding box of this volume, in unscaled coordinates. - mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box; + BoundingBoxf3 m_transformed_convex_hull_bounding_box; // Whether or not is needed to recalculate the transformed convex hull bounding box. - mutable bool m_transformed_convex_hull_bounding_box_dirty; + bool m_transformed_convex_hull_bounding_box_dirty; public: // Color of the triangles / quads held by this volume. @@ -568,8 +568,8 @@ public: // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null - bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state); - bool check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut); + bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const; + bool check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) const; void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); From fbbbf92abf0d203fa62b7f178470b019dde03a8b Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Tue, 20 Apr 2021 18:12:08 +0200 Subject: [PATCH 103/154] creality.ini: disable explicit ABL for CR6-SE (#6383) this is also better in line with Creality's intended behavior --- resources/profiles/Creality.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 95ce8a0dce7..3dab515ab5e 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -969,7 +969,7 @@ printer_model = CR5PROH printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_CR5PROH\nPRINTER_HAS_BOWDEN [printer:Creality CR-6 SE] -inherits = *common*; *fastabl*; *pauseprint* +inherits = *common*; *pauseprint* bed_shape = 5x0,230x0,230x235,5x235 printer_model = CR6SE printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_CR6SE\nPRINTER_HAS_BOWDEN From a24073ad7ff23536a5891aebfb2afe1eac50fd54 Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Tue, 20 Apr 2021 18:13:21 +0200 Subject: [PATCH 104/154] Revert "creality.ini: Extrudr NX2 slightly lower temps" This reverts commit 41c56f2eb8a48ef5530938da08909365108f686d. --- resources/profiles/Creality.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 3dab515ab5e..8420e502e41 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -655,9 +655,9 @@ filament_spool_weight = 256 [filament:Extrudr PLA NX2 @CREALITY] inherits = *PLA* filament_vendor = Extrudr -temperature = 195 +temperature = 200 bed_temperature = 60 -first_layer_temperature = 200 +first_layer_temperature = 205 first_layer_bed_temperature = 60 filament_cost = 23.63 filament_density = 1.3 From 45465f3322555a8edeceef66167fe5ad6e859e2f Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Tue, 20 Apr 2021 20:35:49 +0200 Subject: [PATCH 105/154] Bumped up version to 0.0.16 --- resources/profiles/Creality.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 8420e502e41..52481552507 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -5,7 +5,7 @@ name = Creality # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.15 +config_version = 0.0.16 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% From 4eaa1217cb29d12981c17f40cc05eca52ad26e8e Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Tue, 20 Apr 2021 20:38:27 +0200 Subject: [PATCH 106/154] 0.0.16 Updated CR6-SE start g-code. Added and updated filament profiles. --- resources/profiles/Creality.idx | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx index 2833b8afbb4..c1242f27ecd 100644 --- a/resources/profiles/Creality.idx +++ b/resources/profiles/Creality.idx @@ -1,4 +1,5 @@ min_slic3r_version = 2.3.1-beta +0.0.16 Updated CR6-SE start g-code. Added and updated filament profiles. 0.0.15 Added new printer models, filament profiles. Various improvements. min_slic3r_version = 2.3.0-rc2 0.0.14 Optimized start g-code. Added filament profile. Various improvements. From 6f643ba6d3f4e7d7c7b97d77d130a7bce1f74ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 21 Apr 2021 06:08:46 +0200 Subject: [PATCH 107/154] Added missing include (GCC 9.3) --- src/slic3r/GUI/PrintHostDialogs.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 762450c5374..935746a64e8 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include From b149169eca60161e0d9e4736969f5f68f7eeaa87 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 12:09:36 +0200 Subject: [PATCH 108/154] 1) Moved first_layer_heigth frrom PrintObjectConfig to PrintConfig. Thus the first_layer_height is no more object specific. That makes a lot of sense due to the brim calculation being performed over all layers at once and due to future merging of supports of different objects at first layer. 2) Because now first_layer_height is print specific, the relative first layer height derived from the object layer height was partially disabled: First the relative first layer height is converted to an absolute value when importing config, second the side text was changed from "mm or %" to "mm". Still the UI allows entering %. Both changes may be controversial, let's wait for user feedback. --- src/libslic3r/Config.cpp | 4 ++-- src/libslic3r/Flow.cpp | 7 ++++--- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/Preset.cpp | 7 +++++++ src/libslic3r/PrintConfig.cpp | 8 +++----- src/libslic3r/PrintConfig.hpp | 4 ++-- src/libslic3r/Slicing.cpp | 4 ++-- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 6755a637813..5db1d8179bf 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -471,8 +471,8 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, { t_config_option_key opt_key = opt_key_src; std::string value = value_src; - // Both opt_key and value may be modified by _handle_legacy(). - // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by _handle_legacy(). + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). this->handle_legacy(opt_key, value); if (opt_key.empty()) // Ignore the option. diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 1645bf683a1..56d537c3986 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -238,13 +238,14 @@ Flow support_material_flow(const PrintObject *object, float layer_height) Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) { - const auto &width = (object->print()->config().first_layer_extrusion_width.value > 0) ? object->print()->config().first_layer_extrusion_width : object->config().support_material_extrusion_width; + const PrintConfig &print_config = object->print()->config(); + const auto &width = (print_config.first_layer_extrusion_width.value > 0) ? print_config.first_layer_extrusion_width : object->config().support_material_extrusion_width; return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (width.value > 0) ? width : object->config().extrusion_width, - float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), - (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value))); + float(print_config.nozzle_diameter.get_at(object->config().support_material_extruder-1)), + (layer_height > 0.f) ? layer_height : float(print_config.first_layer_height.get_abs_value(object->config().layer_height.value))); } Flow support_material_interface_flow(const PrintObject *object, float layer_height) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a7994081090..0d65b712477 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1111,7 +1111,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Write some terse information on the slicing parameters. const PrintObject *first_object = print.objects().front(); const double layer_height = first_object->config().layer_height.value; - const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height); + const double first_layer_height = print.config().first_layer_height.get_abs_value(layer_height); for (const PrintRegion* region : print.regions()) { _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width()); _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, layer_height).width()); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 7db61a20f13..c6a86b7193c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -296,6 +296,13 @@ void Preset::normalize(DynamicPrintConfig &config) if (auto *gap_fill_enabled = config.option("gap_fill_enabled", false); gap_fill_enabled) gap_fill_enabled->value = false; } + if (auto *first_layer_height = config.option("first_layer_height", false); first_layer_height && first_layer_height->percent) + if (const auto *layer_height = config.option("layer_height", false); layer_height) { + // Legacy conversion - first_layer_height moved from PrintObject setting to a Print setting, thus we are getting rid of the dependency + // of first_layer_height on PrintObject specific layer_height. Covert the first layer heigth to an absolute value. + first_layer_height->value = first_layer_height->get_abs_value(layer_height->value); + first_layer_height->percent = false; + } } std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5516b298d31..9f09bc9f34d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -995,10 +995,8 @@ void PrintConfigDef::init_fff_params() def->label = L("First layer height"); def->category = L("Layers and Perimeters"); def->tooltip = L("When printing with very low layer heights, you might still want to print a thicker " - "bottom layer to improve adhesion and tolerance for non perfect build plates. " - "This can be expressed as an absolute value or as a percentage (for example: 150%) " - "over the default layer height."); - def->sidetext = L("mm or %"); + "bottom layer to improve adhesion and tolerance for non perfect build plates."); + def->sidetext = L("mm"); def->ratio_over = "layer_height"; def->set_default_value(new ConfigOptionFloatOrPercent(0.35, false)); @@ -3628,7 +3626,7 @@ std::string FullPrintConfig::validate() return "--layer-height must be a multiple of print resolution"; // --first-layer-height - if (this->get_abs_value("first_layer_height") <= 0) + if (first_layer_height.value <= 0) return "Invalid value for --first-layer-height"; // --filament-diameter diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index aab5096624c..74cb5c7748f 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -496,7 +496,6 @@ public: ConfigOptionBool dont_support_bridges; ConfigOptionFloat elefant_foot_compensation; ConfigOptionFloatOrPercent extrusion_width; - ConfigOptionFloatOrPercent first_layer_height; ConfigOptionBool infill_only_where_needed; // Force the generation of solid shells between adjacent materials/volumes. ConfigOptionBool interface_shells; @@ -555,7 +554,6 @@ protected: OPT_PTR(dont_support_bridges); OPT_PTR(elefant_foot_compensation); OPT_PTR(extrusion_width); - OPT_PTR(first_layer_height); OPT_PTR(infill_only_where_needed); OPT_PTR(interface_shells); OPT_PTR(layer_height); @@ -950,6 +948,7 @@ public: ConfigOptionFloat first_layer_acceleration; ConfigOptionInts first_layer_bed_temperature; ConfigOptionFloatOrPercent first_layer_extrusion_width; + ConfigOptionFloatOrPercent first_layer_height; ConfigOptionFloatOrPercent first_layer_speed; ConfigOptionInts first_layer_temperature; ConfigOptionInts full_fan_speed_layer; @@ -1025,6 +1024,7 @@ protected: OPT_PTR(first_layer_acceleration); OPT_PTR(first_layer_bed_temperature); OPT_PTR(first_layer_extrusion_width); + OPT_PTR(first_layer_height); OPT_PTR(first_layer_speed); OPT_PTR(first_layer_temperature); OPT_PTR(full_fan_speed_layer); diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index d0b1e9ce267..98a5923aa07 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -64,9 +64,9 @@ SlicingParameters SlicingParameters::create_from_config( coordf_t object_height, const std::vector &object_extruders) { - coordf_t first_layer_height = (object_config.first_layer_height.value <= 0) ? + coordf_t first_layer_height = (print_config.first_layer_height.value <= 0) ? object_config.layer_height.value : - object_config.first_layer_height.get_abs_value(object_config.layer_height.value); + print_config.first_layer_height.get_abs_value(object_config.layer_height.value); // If object_config.support_material_extruder == 0 resp. object_config.support_material_interface_extruder == 0, // print_config.nozzle_diameter.get_at(size_t(-1)) returns the 0th nozzle diameter, // which is consistent with the requirement that if support_material_extruder == 0 resp. support_material_interface_extruder == 0, From 96c1193156f23670e116fbe1063430b1e06c069d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 13:30:32 +0200 Subject: [PATCH 109/154] Fixed unit tests. --- tests/fff_print/test_support_material.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fff_print/test_support_material.cpp b/tests/fff_print/test_support_material.cpp index 1b85532d315..442db7654f2 100644 --- a/tests/fff_print/test_support_material.cpp +++ b/tests/fff_print/test_support_material.cpp @@ -29,7 +29,7 @@ SCENARIO("SupportMaterial: support_layers_z and contact_distance", "[SupportMate { ConstSupportLayerPtrsAdaptor support_layers = print.objects().front()->support_layers(); - first_support_layer_height_ok = support_layers.front()->print_z == print.default_object_config().first_layer_height.value; + first_support_layer_height_ok = support_layers.front()->print_z == print.config().first_layer_height.value; layer_height_minimum_ok = true; layer_height_maximum_ok = true; From 73289e9e8edfc568c3a345789eee9c089bd3da82 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 21 Apr 2021 13:49:24 +0200 Subject: [PATCH 110/154] Updated splashscreen images --- resources/icons/splashscreen-gcodepreview.jpg | Bin 127316 -> 237955 bytes resources/icons/splashscreen.jpg | Bin 128296 -> 226513 bytes src/slic3r/GUI/GUI_App.cpp | 5 +++-- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/icons/splashscreen-gcodepreview.jpg b/resources/icons/splashscreen-gcodepreview.jpg index 3bae384935ecadff5051a4d2f06f9bef7195a7e5..481c4a6e12a5776d90e4d105021c0b114f9ee459 100644 GIT binary patch delta 231325 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 #+(YgB=D(RW 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

%X! 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 z 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 F|Jj8&ct`*M diff --git a/resources/icons/splashscreen.jpg b/resources/icons/splashscreen.jpg index 754e2458806530847a7b15db5311f1e327ee7216..08236bcccc42082fbaabebe680a47c8f29c359f9 100644 GIT binary patch literal 226513 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%0Dt(3X@vj%ao{}p@LE3kw{`>6^TwN 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 Date: Wed, 21 Apr 2021 14:34:43 +0200 Subject: [PATCH 111/154] Further fixes of the first_layer_height refactoring. --- src/libslic3r/Flow.cpp | 13 +++---------- src/libslic3r/GCode.cpp | 3 ++- src/libslic3r/Print.cpp | 8 ++++---- src/libslic3r/Slicing.cpp | 4 ++-- src/slic3r/GUI/ConfigManipulation.cpp | 6 +++--- src/slic3r/GUI/PresetHints.cpp | 3 ++- 6 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 56d537c3986..9f4730261e1 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -89,18 +89,11 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloat if (opt->percent) { auto opt_key_layer_height = first_layer ? "first_layer_height" : "layer_height"; - auto opt_layer_height = config.option(opt_key_layer_height); + auto opt_layer_height = config.option(opt_key_layer_height); if (opt_layer_height == nullptr) throw_on_missing_variable(opt_key, opt_key_layer_height); - double layer_height = opt_layer_height->getFloat(); - if (first_layer && static_cast(opt_layer_height)->percent) { - // first_layer_height depends on layer_height. - opt_layer_height = config.option("layer_height"); - if (opt_layer_height == nullptr) - throw_on_missing_variable(opt_key, "layer_height"); - layer_height *= 0.01 * opt_layer_height->getFloat(); - } - return opt->get_abs_value(layer_height); + assert(! first_layer || ! static_cast(opt_layer_height)->percent); + return opt->get_abs_value(opt_layer_height->getFloat()); } if (opt->value == 0.) { diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0d65b712477..c5b28b3a01b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1111,7 +1111,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Write some terse information on the slicing parameters. const PrintObject *first_object = print.objects().front(); const double layer_height = first_object->config().layer_height.value; - const double first_layer_height = print.config().first_layer_height.get_abs_value(layer_height); + assert(! print.config().first_layer_height.percent); + const double first_layer_height = print.config().first_layer_height.value; for (const PrintRegion* region : print.regions()) { _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width()); _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, layer_height).width()); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ce5bf1b2947..7fcb7529783 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1464,7 +1464,8 @@ std::string Print::validate(std::string* warning) const } // validate first_layer_height - double first_layer_height = object->config().get_abs_value("first_layer_height"); + assert(! m_config.first_layer_height.percent); + double first_layer_height = m_config.first_layer_height.value; double first_layer_min_nozzle_diameter; if (object->has_raft()) { // if we have raft layers, only support material extruder is used on first layer @@ -1561,9 +1562,8 @@ BoundingBox Print::total_bounding_box() const double Print::skirt_first_layer_height() const { - if (m_objects.empty()) - throw Slic3r::InvalidArgument("skirt_first_layer_height() can't be called without PrintObjects"); - return m_objects.front()->config().get_abs_value("first_layer_height"); + assert(! m_config.first_layer_height.percent); + return m_config.first_layer_height.value; } Flow Print::brim_flow() const diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 98a5923aa07..82b3cf1b662 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -64,9 +64,9 @@ SlicingParameters SlicingParameters::create_from_config( coordf_t object_height, const std::vector &object_extruders) { + assert(! print_config.first_layer_height.percent); coordf_t first_layer_height = (print_config.first_layer_height.value <= 0) ? - object_config.layer_height.value : - print_config.first_layer_height.get_abs_value(object_config.layer_height.value); + object_config.layer_height.value : print_config.first_layer_height.value; // If object_config.support_material_extruder == 0 resp. object_config.support_material_interface_extruder == 0, // print_config.nozzle_diameter.get_at(size_t(-1)) returns the 0th nozzle diameter, // which is consistent with the requirement that if support_material_extruder == 0 resp. support_material_interface_extruder == 0, diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index cd7805a8808..d557585384c 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -45,7 +45,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con // layer_height shouldn't be equal to zero if (config->opt_float("layer_height") < EPSILON) { - const wxString msg_text = _(L("Zero layer height is not valid.\n\nThe layer height will be reset to 0.01.")); + const wxString msg_text = _(L("Layer height is not valid.\n\nThe layer height will be reset to 0.01.")); wxMessageDialog dialog(nullptr, msg_text, _(L("Layer height")), wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; @@ -55,9 +55,9 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con is_msg_dlg_already_exist = false; } - if (fabs(config->option("first_layer_height")->value - 0) < EPSILON) + if (config->option("first_layer_height")->value < EPSILON) { - const wxString msg_text = _(L("Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01.")); + const wxString msg_text = _(L("First layer height is not valid.\n\nThe first layer height will be reset to 0.01.")); wxMessageDialog dialog(nullptr, msg_text, _(L("First layer height")), wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 181dcfda47c..0e2b7f8364c 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -86,7 +86,8 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle // Print config values double layer_height = print_config.opt_float("layer_height"); - double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + assert(! print_config.option("first_layer_height")->percent); + double first_layer_height = print_config.opt_float("first_layer_height"); double support_material_speed = print_config.opt_float("support_material_speed"); double support_material_interface_speed = print_config.get_abs_value("support_material_interface_speed", support_material_speed); double bridge_speed = print_config.opt_float("bridge_speed"); From 17a63f974e8b213420b386436254301061c9b54d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 15:31:06 +0200 Subject: [PATCH 112/154] Fixing unit tests --- tests/fff_print/test_flow.cpp | 2 +- tests/libslic3r/test_placeholder_parser.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index 08ba15a84a6..dc73f4b6ec9 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -24,7 +24,7 @@ SCENARIO("Extrusion width specifics", "[Flow]") { { "skirts", 1 }, { "perimeters", 3 }, { "fill_density", "40%" }, - { "first_layer_height", "100%" } + { "first_layer_height", 0.3 } }); WHEN("first layer width set to 2mm") { diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index e632dc70577..8c56afc6d0c 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -14,9 +14,12 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { { "nozzle_diameter", "0.6;0.6;0.6;0.6" }, { "temperature", "357;359;363;378" } }); - // To test the "first_layer_extrusion_width" over "first_layer_heigth" over "layer_height" chain. - config.option("first_layer_height")->value = 150.; - config.option("first_layer_height")->percent = true; + // To test the "first_layer_extrusion_width" over "first_layer_heigth". + // "first_layer_heigth" over "layer_height" is no more supported after first_layer_height was moved from PrintObjectConfig to PrintConfig. +// config.option("first_layer_height")->value = 150.; +// config.option("first_layer_height")->percent = true; + config.option("first_layer_height")->value = 1.5 * config.opt_float("layer_height"); + config.option("first_layer_height")->percent = false; // To let the PlaceholderParser throw when referencing first_layer_speed if it is set to percent, as the PlaceholderParser does not know // a percent to what. config.option("first_layer_speed")->value = 50.; @@ -50,7 +53,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("math: int(-13.4)") { REQUIRE(parser.process("{int(-13.4)}") == "-13"); } // Test the "coFloatOrPercent" and "xxx_extrusion_width" substitutions. - // first_layer_extrusion_width ratio_over first_layer_heigth ratio_over layer_height + // first_layer_extrusion_width ratio_over first_layer_heigth. SECTION("perimeter_extrusion_width") { REQUIRE(std::stod(parser.process("{perimeter_extrusion_width}")) == Approx(0.67500001192092896)); } SECTION("first_layer_extrusion_width") { REQUIRE(std::stod(parser.process("{first_layer_extrusion_width}")) == Approx(0.9)); } SECTION("support_material_xy_spacing") { REQUIRE(std::stod(parser.process("{support_material_xy_spacing}")) == Approx(0.3375)); } From fefdc675f0f96da9c20cdd4ae18f2d4995c79f14 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 15:38:00 +0200 Subject: [PATCH 113/154] Fixing perl unit tests --- t/flow.t | 2 +- t/layers.t | 2 +- t/multi.t | 2 +- t/shells.t | 12 ++++++------ t/thin.t | 2 +- xs/t/15_config.t | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/t/flow.t b/t/flow.t index 4d7ee5ca2aa..50c4916049c 100644 --- a/t/flow.t +++ b/t/flow.t @@ -21,7 +21,7 @@ use Slic3r::Test; $config->set('fill_density', 0.4); $config->set('bottom_solid_layers', 1); $config->set('first_layer_extrusion_width', 2); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', $config->layer_height); $config->set('filament_diameter', [ 3.0 ]); $config->set('nozzle_diameter', [ 0.5 ]); diff --git a/t/layers.t b/t/layers.t index a9f7dfe39f0..4d958808a6a 100644 --- a/t/layers.t +++ b/t/layers.t @@ -49,7 +49,7 @@ use Slic3r::Test qw(_eq); $config->set('first_layer_height', 0.2); ok $test->(), "absolute first layer height"; - $config->set('first_layer_height', '60%'); + $config->set('first_layer_height', 0.6 * $config->layer_height); ok $test->(), "relative first layer height"; $config->set('z_offset', 0.9); diff --git a/t/multi.t b/t/multi.t index 8e7225bec7b..e74a7a1a8b0 100644 --- a/t/multi.t +++ b/t/multi.t @@ -181,7 +181,7 @@ use Slic3r::Test; my $config = Slic3r::Config::new_from_defaults; $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('layer_height', 0.4); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', $config->layer_height); $config->set('skirts', 0); my $print = Slic3r::Test::init_print($model, config => $config); diff --git a/t/shells.t b/t/shells.t index 47b5c8881d7..b6d6bf9beca 100644 --- a/t/shells.t +++ b/t/shells.t @@ -84,7 +84,7 @@ use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.3); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', $config->layer_height); $config->set('bottom_solid_layers', 0); $config->set('top_solid_layers', 3); $config->set('cooling', [ 0 ]); @@ -119,7 +119,7 @@ use Slic3r::Test; $config->set('cooling', [ 0 ]); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('layer_height', 0.4); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', $config->layer_height); $config->set('extrusion_width', 0.55); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 0); @@ -142,7 +142,7 @@ use Slic3r::Test; $config->set('cooling', [ 0 ]); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('layer_height', 0.4); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', $config->layer_height); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 3); $config->set('solid_infill_speed', 99); @@ -170,7 +170,7 @@ use Slic3r::Test; $config->set('spiral_vase', 1); $config->set('bottom_solid_layers', 0); $config->set('skirts', 0); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', $config->layer_height); $config->set('start_gcode', ''); $config->set('temperature', [200]); $config->set('first_layer_temperature', [205]); @@ -231,7 +231,7 @@ use Slic3r::Test; $config->set('bottom_solid_layers', 0); $config->set('retract_layer_change', [0]); $config->set('skirts', 0); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', $config->layer_height); $config->set('layer_height', 0.4); $config->set('start_gcode', ''); # $config->set('use_relative_e_distances', 1); @@ -310,7 +310,7 @@ use Slic3r::Test; # $config->set('spiral_vase', 1); # $config->set('bottom_solid_layers', 0); # $config->set('skirts', 0); -# $config->set('first_layer_height', '100%'); +# $config->set('first_layer_height', $config->layer_height); # $config->set('start_gcode', ''); # # my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); diff --git a/t/thin.t b/t/thin.t index 9147236ee5f..50e7abc9500 100644 --- a/t/thin.t +++ b/t/thin.t @@ -18,7 +18,7 @@ use Slic3r::Test; if (0) { my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); - $config->set('first_layer_height', '100%'); + $config->set('first_layer_height', $config->layer_height); $config->set('extrusion_width', 0.5); $config->set('first_layer_extrusion_width', '200%'); # check this one too $config->set('skirts', 0); diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 55b67910155..8981e00954b 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 147; +use Test::More tests => 146; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); @@ -70,10 +70,10 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; is $config->opt_serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; - $config->set('first_layer_height', '50%'); + $config->set('first_layer_height', $config->layer_height); $config->get_abs_value('first_layer_height'); ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; - is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; +# is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; # Uh-oh, we have no point option to test at the moment #ok $config->set('print_center', [50,80]), 'valid point coordinates'; From cdb7c8e229a18779ce44d8fc71248460c0807db9 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 15:40:43 +0200 Subject: [PATCH 114/154] One more perl unit test fix --- t/shells.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/shells.t b/t/shells.t index b6d6bf9beca..29bc0b5f008 100644 --- a/t/shells.t +++ b/t/shells.t @@ -231,8 +231,8 @@ use Slic3r::Test; $config->set('bottom_solid_layers', 0); $config->set('retract_layer_change', [0]); $config->set('skirts', 0); - $config->set('first_layer_height', $config->layer_height); $config->set('layer_height', 0.4); + $config->set('first_layer_height', $config->layer_height); $config->set('start_gcode', ''); # $config->set('use_relative_e_distances', 1); $config->validate; From fbd1be5ce1d6c007ddd745f9e864fcde1d6ec2b9 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 15:43:41 +0200 Subject: [PATCH 115/154] Another last perl unit test fix --- xs/t/15_config.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 8981e00954b..a79fe7a374d 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -70,7 +70,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; is $config->opt_serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; - $config->set('first_layer_height', $config->layer_height); + $config->set('first_layer_height', $config->get('layer_height')); $config->get_abs_value('first_layer_height'); ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; # is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; From f5aa778fee67ac2920b27f2896fd4cdd0ded18d5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 15:46:47 +0200 Subject: [PATCH 116/154] Yet another Perl test --- xs/t/15_config.t | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/xs/t/15_config.t b/xs/t/15_config.t index a79fe7a374d..6326cae106b 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 146; +use Test::More tests => 144; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); @@ -70,9 +70,10 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; is $config->opt_serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; - $config->set('first_layer_height', $config->get('layer_height')); - $config->get_abs_value('first_layer_height'); - ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; +# This is no more supported after first_layer_height was moved from PrintObjectConfig to PrintConfig. +# $config->set('first_layer_height', $config->get('layer_height')); +# $config->get_abs_value('first_layer_height'); +# ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; # is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; # Uh-oh, we have no point option to test at the moment From 237a35e24e7efa3e569323411ee4ce0ffcfe2bd2 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 15:47:28 +0200 Subject: [PATCH 117/154] and the final Perl unit test fix --- xs/t/15_config.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 6326cae106b..4d032019c2e 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 144; +use Test::More tests => 143; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); From f3af54744100a0890cf755a5fe619e5cdc38341a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Apr 2021 16:02:25 +0200 Subject: [PATCH 118/154] New custom backend for libnest2d using libslic3r types Adapted to new clipper->eigen mod --- src/libnest2d/CMakeLists.txt | 9 +- .../backends/clipper/clipper_polygon.hpp | 75 ---- .../libnest2d/backends/clipper/geometries.hpp | 356 ------------------ .../backends/libslic3r/geometries.hpp | 272 +++++++++++++ .../include/libnest2d/geometry_traits.hpp | 42 ++- .../include/libnest2d/geometry_traits_nfp.hpp | 38 +- src/libnest2d/include/libnest2d/libnest2d.hpp | 4 + src/libnest2d/include/libnest2d/nester.hpp | 52 +-- .../libnest2d/placers/bottomleftplacer.hpp | 38 +- .../include/libnest2d/placers/nfpplacer.hpp | 6 +- .../include/libnest2d/utils/boost_alg.hpp | 22 +- src/libslic3r/Arrange.cpp | 44 +-- src/libslic3r/ExPolygon.hpp | 2 + src/libslic3r/Polygon.hpp | 12 + src/libslic3r/SLA/AGGRaster.hpp | 7 - src/libslic3r/SLA/RasterBase.hpp | 3 - src/libslic3r/SLA/SupportPointGenerator.cpp | 7 +- src/libslic3r/SLAPrint.hpp | 5 +- src/libslic3r/SLAPrintSteps.cpp | 133 +++---- tests/libnest2d/CMakeLists.txt | 2 +- tests/libnest2d/libnest2d_tests_main.cpp | 292 +++++++------- 21 files changed, 656 insertions(+), 765 deletions(-) delete mode 100644 src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp delete mode 100644 src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp create mode 100644 src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 3892ed30bc6..c18dc31cb4c 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -12,11 +12,8 @@ set(LIBNEST2D_SRCFILES include/libnest2d/placers/bottomleftplacer.hpp include/libnest2d/placers/nfpplacer.hpp include/libnest2d/selections/selection_boilerplate.hpp - #include/libnest2d/selections/filler.hpp include/libnest2d/selections/firstfit.hpp - #include/libnest2d/selections/djd_heuristic.hpp - include/libnest2d/backends/clipper/geometries.hpp - include/libnest2d/backends/clipper/clipper_polygon.hpp + include/libnest2d/backends/libslic3r/geometries.hpp include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp include/libnest2d/optimizers/nlopt/simplex.hpp include/libnest2d/optimizers/nlopt/subplex.hpp @@ -27,5 +24,5 @@ set(LIBNEST2D_SRCFILES add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES}) target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) -target_link_libraries(libnest2d PUBLIC clipper NLopt::nlopt TBB::tbb Boost::boost) -target_compile_definitions(libnest2d PUBLIC LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_clipper) +target_link_libraries(libnest2d PUBLIC NLopt::nlopt TBB::tbb Boost::boost libslic3r) +target_compile_definitions(libnest2d PUBLIC LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_libslic3r) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp deleted file mode 100644 index d4fcd7af335..00000000000 --- a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef CLIPPER_POLYGON_HPP -#define CLIPPER_POLYGON_HPP - -#include - -namespace ClipperLib { - -struct Polygon { - Path Contour; - Paths Holes; - - inline Polygon() = default; - - inline explicit Polygon(const Path& cont): Contour(cont) {} -// inline explicit Polygon(const Paths& holes): -// Holes(holes) {} - inline Polygon(const Path& cont, const Paths& holes): - Contour(cont), Holes(holes) {} - - inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {} -// inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} - inline Polygon(Path&& cont, Paths&& holes): - Contour(std::move(cont)), Holes(std::move(holes)) {} -}; - -#if 0 -inline IntPoint& operator +=(IntPoint& p, const IntPoint& pa ) { - // This could be done with SIMD - - p.x() += pa.x(); - p.y() += pa.y(); - return p; -} - -inline IntPoint operator+(const IntPoint& p1, const IntPoint& p2) { - IntPoint ret = p1; - ret += p2; - return ret; -} - -inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) { - p.x() -= pa.x(); - p.y() -= pa.y(); - return p; -} - -inline IntPoint operator -(const IntPoint& p ) { - IntPoint ret = p; - ret.x() = -ret.x(); - ret.y() = -ret.y(); - return ret; -} - -inline IntPoint operator-(const IntPoint& p1, const IntPoint& p2) { - IntPoint ret = p1; - ret -= p2; - return ret; -} - -inline IntPoint& operator *=(IntPoint& p, const IntPoint& pa ) { - p.x() *= pa.x(); - p.y() *= pa.y(); - return p; -} - -inline IntPoint operator*(const IntPoint& p1, const IntPoint& p2) { - IntPoint ret = p1; - ret *= p2; - return ret; -} -#endif - -} - -#endif // CLIPPER_POLYGON_HPP diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp deleted file mode 100644 index 5999ebf2a06..00000000000 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ /dev/null @@ -1,356 +0,0 @@ -#ifndef CLIPPER_BACKEND_HPP -#define CLIPPER_BACKEND_HPP - -#include -#include -#include -#include -#include - -#include -#include - -#include "clipper_polygon.hpp" - -namespace libnest2d { - -// Aliases for convinience -using PointImpl = ClipperLib::IntPoint; -using PathImpl = ClipperLib::Path; -using HoleStore = ClipperLib::Paths; -using PolygonImpl = ClipperLib::Polygon; - -template<> struct ShapeTag { using Type = PolygonTag; }; -template<> struct ShapeTag { using Type = PathTag; }; -template<> struct ShapeTag { using Type = PointTag; }; - -// Type of coordinate units used by Clipper. Enough to specialize for point, -// the rest of the types will work (Path, Polygon) -template<> struct CoordType { - using Type = ClipperLib::cInt; - static const constexpr ClipperLib::cInt MM_IN_COORDS = 1000000; -}; - -// Enough to specialize for path, it will work for multishape and Polygon -template<> struct PointType { using Type = PointImpl; }; - -// This is crucial. CountourType refers to itself by default, so we don't have -// to secialize for clipper Path. ContourType::Type is PathImpl. -template<> struct ContourType { using Type = PathImpl; }; - -// The holes are contained in Clipper::Paths -template<> struct HolesContainer { using Type = ClipperLib::Paths; }; - -namespace pointlike { - -// Tell libnest2d how to extract the X coord from a ClipperPoint object -template<> inline ClipperLib::cInt x(const PointImpl& p) -{ - return p.x(); -} - -// Tell libnest2d how to extract the Y coord from a ClipperPoint object -template<> inline ClipperLib::cInt y(const PointImpl& p) -{ - return p.y(); -} - -// Tell libnest2d how to extract the X coord from a ClipperPoint object -template<> inline ClipperLib::cInt& x(PointImpl& p) -{ - return p.x(); -} - -// Tell libnest2d how to extract the Y coord from a ClipperPoint object -template<> inline ClipperLib::cInt& y(PointImpl& p) -{ - return p.y(); -} - -} - -// Using the libnest2d default area implementation -#define DISABLE_BOOST_AREA - -namespace shapelike { - -template<> -inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag&) -{ - #define DISABLE_BOOST_OFFSET - - using ClipperLib::ClipperOffset; - using ClipperLib::jtSquare; - using ClipperLib::etClosedPolygon; - using ClipperLib::Paths; - - Paths result; - - try { - ClipperOffset offs; - offs.AddPath(sh.Contour, jtSquare, etClosedPolygon); - offs.AddPaths(sh.Holes, jtSquare, etClosedPolygon); - offs.Execute(result, static_cast(distance)); - } catch (ClipperLib::clipperException &) { - throw GeometryException(GeomErr::OFFSET); - } - - // Offsetting reverts the orientation and also removes the last vertex - // so boost will not have a closed polygon. - - // we plan to replace contours - sh.Holes.clear(); - - bool found_the_contour = false; - for(auto& r : result) { - if(ClipperLib::Orientation(r)) { - // We don't like if the offsetting generates more than one contour - // but throwing would be an overkill. Instead, we should warn the - // caller about the inability to create correct geometries - if(!found_the_contour) { - sh.Contour = std::move(r); - ClipperLib::ReversePath(sh.Contour); - auto front_p = sh.Contour.front(); - sh.Contour.emplace_back(std::move(front_p)); - found_the_contour = true; - } else { - dout() << "Warning: offsetting result is invalid!"; - /* TODO warning */ - } - } else { - // TODO If there are multiple contours we can't be sure which hole - // belongs to the first contour. (But in this case the situation is - // bad enough to let it go...) - sh.Holes.emplace_back(std::move(r)); - ClipperLib::ReversePath(sh.Holes.back()); - auto front_p = sh.Holes.back().front(); - sh.Holes.back().emplace_back(std::move(front_p)); - } - } -} - -template<> -inline void offset(PathImpl& sh, TCoord distance, const PathTag&) -{ - PolygonImpl p(std::move(sh)); - offset(p, distance, PolygonTag()); - sh = p.Contour; -} - -// Tell libnest2d how to make string out of a ClipperPolygon object -template<> inline std::string toString(const PolygonImpl& sh) -{ - std::stringstream ss; - - ss << "Contour {\n"; - for(auto p : sh.Contour) { - ss << "\t" << p.x() << " " << p.y() << "\n"; - } - ss << "}\n"; - - for(auto& h : sh.Holes) { - ss << "Holes {\n"; - for(auto p : h) { - ss << "\t{\n"; - ss << "\t\t" << p.x() << " " << p.y() << "\n"; - ss << "\t}\n"; - } - ss << "}\n"; - } - - return ss.str(); -} - -template<> -inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) -{ - PolygonImpl p; - p.Contour = path; - p.Holes = holes; - - return p; -} - -template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { - PolygonImpl p; - p.Contour.swap(path); - p.Holes.swap(holes); - - return p; -} - -template<> -inline const THolesContainer& holes(const PolygonImpl& sh) -{ - return sh.Holes; -} - -template<> inline THolesContainer& holes(PolygonImpl& sh) -{ - return sh.Holes; -} - -template<> -inline TContour& hole(PolygonImpl& sh, unsigned long idx) -{ - return sh.Holes[idx]; -} - -template<> -inline const TContour& hole(const PolygonImpl& sh, - unsigned long idx) -{ - return sh.Holes[idx]; -} - -template<> inline size_t holeCount(const PolygonImpl& sh) -{ - return sh.Holes.size(); -} - -template<> inline PathImpl& contour(PolygonImpl& sh) -{ - return sh.Contour; -} - -template<> -inline const PathImpl& contour(const PolygonImpl& sh) -{ - return sh.Contour; -} - -#define DISABLE_BOOST_TRANSLATE -template<> -inline void translate(PolygonImpl& sh, const PointImpl& offs) -{ - for(auto& p : sh.Contour) { p += offs; } - for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; } -} - -#define DISABLE_BOOST_ROTATE -template<> -inline void rotate(PolygonImpl& sh, const Radians& rads) -{ - using Coord = TCoord; - - auto cosa = rads.cos(); - auto sina = rads.sin(); - - for(auto& p : sh.Contour) { - p = { - static_cast(p.x() * cosa - p.y() * sina), - static_cast(p.x() * sina + p.y() * cosa) - }; - } - for(auto& hole : sh.Holes) for(auto& p : hole) { - p = { - static_cast(p.x() * cosa - p.y() * sina), - static_cast(p.x() * sina + p.y() * cosa) - }; - } -} - -} // namespace shapelike - -#define DISABLE_BOOST_NFP_MERGE -inline TMultiShape clipper_execute( - ClipperLib::Clipper& clipper, - ClipperLib::ClipType clipType, - ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, - ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) -{ - TMultiShape retv; - - ClipperLib::PolyTree result; - clipper.Execute(clipType, result, subjFillType, clipFillType); - - retv.reserve(static_cast(result.Total())); - - std::function processHole; - - auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) { - PolygonImpl poly; - poly.Contour.swap(pptr->Contour); - - assert(!pptr->IsHole()); - - if(!poly.Contour.empty() ) { - auto front_p = poly.Contour.front(); - auto &back_p = poly.Contour.back(); - if(front_p.x() != back_p.x() || front_p.y() != back_p.x()) - poly.Contour.emplace_back(front_p); - } - - for(auto h : pptr->Childs) { processHole(h, poly); } - retv.push_back(poly); - }; - - processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) - { - poly.Holes.emplace_back(std::move(pptr->Contour)); - - assert(pptr->IsHole()); - - if(!poly.Contour.empty() ) { - auto front_p = poly.Contour.front(); - auto &back_p = poly.Contour.back(); - if(front_p.x() != back_p.x() || front_p.y() != back_p.x()) - poly.Contour.emplace_back(front_p); - } - - for(auto c : pptr->Childs) processPoly(c); - }; - - auto traverse = [&processPoly] (ClipperLib::PolyNode *node) - { - for(auto ch : node->Childs) processPoly(ch); - }; - - traverse(&result); - - return retv; -} - -namespace nfp { - -template<> inline TMultiShape -merge(const TMultiShape& shapes) -{ - ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); - - bool closed = true; - bool valid = true; - - for(auto& path : shapes) { - valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); - - for(auto& h : path.Holes) - valid &= clipper.AddPath(h, ClipperLib::ptSubject, closed); - } - - if(!valid) throw GeometryException(GeomErr::MERGE); - - return clipper_execute(clipper, ClipperLib::ctUnion, ClipperLib::pftNegative); -} - -} - -} - -#define DISABLE_BOOST_CONVEX_HULL - -//#define DISABLE_BOOST_SERIALIZE -//#define DISABLE_BOOST_UNSERIALIZE - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4244) -#pragma warning(disable: 4267) -#endif -// All other operators and algorithms are implemented with boost -#include -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#endif // CLIPPER_BACKEND_HPP diff --git a/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp b/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp new file mode 100644 index 00000000000..08439a63e54 --- /dev/null +++ b/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp @@ -0,0 +1,272 @@ +#ifndef CLIPPER_BACKEND_HPP +#define CLIPPER_BACKEND_HPP + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace Slic3r { + +template struct IsVec_ : public std::false_type {}; + +template struct IsVec_< Vec<2, T> >: public std::true_type {}; + +template +static constexpr const bool IsVec = IsVec_>::value; + +template using VecOnly = std::enable_if_t, O>; + +inline Point operator+(const Point& p1, const Point& p2) { + Point ret = p1; + ret += p2; + return ret; +} + +inline Point operator -(const Point& p ) { + Point ret = p; + ret.x() = -ret.x(); + ret.y() = -ret.y(); + return ret; +} + +inline Point operator-(const Point& p1, const Point& p2) { + Point ret = p1; + ret -= p2; + return ret; +} + +inline Point& operator *=(Point& p, const Point& pa ) { + p.x() *= pa.x(); + p.y() *= pa.y(); + return p; +} + +inline Point operator*(const Point& p1, const Point& p2) { + Point ret = p1; + ret *= p2; + return ret; +} + +} // namespace Slic3r + +namespace libnest2d { + +template using Vec = Slic3r::Vec<2, T>; + +// Aliases for convinience +using PointImpl = Slic3r::Point; +using PathImpl = Slic3r::Polygon; +using HoleStore = Slic3r::Polygons; +using PolygonImpl = Slic3r::ExPolygon; + +template<> struct ShapeTag { using Type = PointTag; }; +template<> struct ShapeTag { using Type = PointTag; }; + +template<> struct ShapeTag> { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PolygonTag; }; +template<> struct ShapeTag { using Type = MultiPolygonTag; }; + +// Type of coordinate units used by Clipper. Enough to specialize for point, +// the rest of the types will work (Path, Polygon) +template<> struct CoordType { + using Type = coord_t; + static const constexpr coord_t MM_IN_COORDS = 1000000; +}; + +template<> struct CoordType { + using Type = coord_t; + static const constexpr coord_t MM_IN_COORDS = 1000000; +}; + +// Enough to specialize for path, it will work for multishape and Polygon +template<> struct PointType> { using Type = Slic3r::Vec2crd; }; +template<> struct PointType { using Type = Slic3r::Point; }; +template<> struct PointType { using Type = Slic3r::Point; }; + +// This is crucial. CountourType refers to itself by default, so we don't have +// to secialize for clipper Path. ContourType::Type is PathImpl. +template<> struct ContourType { using Type = Slic3r::Polygon; }; + +// The holes are contained in Clipper::Paths +template<> struct HolesContainer { using Type = Slic3r::Polygons; }; + +template<> +struct OrientationType { + static const constexpr Orientation Value = Orientation::COUNTER_CLOCKWISE; +}; + +template<> +struct ClosureType { + static const constexpr Closure Value = Closure::OPEN; +}; + +template<> struct MultiShape { using Type = Slic3r::ExPolygons; }; +template<> struct ContourType { using Type = Slic3r::Polygon; }; + +// Using the libnest2d default area implementation +#define DISABLE_BOOST_AREA + +namespace shapelike { + +template<> +inline void offset(Slic3r::ExPolygon& sh, coord_t distance, const PolygonTag&) +{ +#define DISABLE_BOOST_OFFSET + auto res = Slic3r::offset_ex(sh, distance, ClipperLib::jtSquare); + if (!res.empty()) sh = res.front(); +} + +template<> +inline void offset(Slic3r::Polygon& sh, coord_t distance, const PathTag&) +{ + auto res = Slic3r::offset(sh, distance, ClipperLib::jtSquare); + if (!res.empty()) sh = res.front(); +} + +// Tell libnest2d how to make string out of a ClipperPolygon object +template<> inline std::string toString(const Slic3r::ExPolygon& sh) +{ + std::stringstream ss; + + ss << "Contour {\n"; + for(auto &p : sh.contour.points) { + ss << "\t" << p.x() << " " << p.y() << "\n"; + } + ss << "}\n"; + + for(auto& h : sh.holes) { + ss << "Holes {\n"; + for(auto p : h.points) { + ss << "\t{\n"; + ss << "\t\t" << p.x() << " " << p.y() << "\n"; + ss << "\t}\n"; + } + ss << "}\n"; + } + + return ss.str(); +} + +template<> +inline Slic3r::ExPolygon create(const Slic3r::Polygon& path, const Slic3r::Polygons& holes) +{ + Slic3r::ExPolygon p; + p.contour = path; + p.holes = holes; + + return p; +} + +template<> inline Slic3r::ExPolygon create(Slic3r::Polygon&& path, Slic3r::Polygons&& holes) { + Slic3r::ExPolygon p; + p.contour.points.swap(path.points); + p.holes.swap(holes); + + return p; +} + +template<> +inline const THolesContainer& holes(const Slic3r::ExPolygon& sh) +{ + return sh.holes; +} + +template<> inline THolesContainer& holes(Slic3r::ExPolygon& sh) +{ + return sh.holes; +} + +template<> +inline Slic3r::Polygon& hole(Slic3r::ExPolygon& sh, unsigned long idx) +{ + return sh.holes[idx]; +} + +template<> +inline const Slic3r::Polygon& hole(const Slic3r::ExPolygon& sh, unsigned long idx) +{ + return sh.holes[idx]; +} + +template<> inline size_t holeCount(const Slic3r::ExPolygon& sh) +{ + return sh.holes.size(); +} + +template<> inline Slic3r::Polygon& contour(Slic3r::ExPolygon& sh) +{ + return sh.contour; +} + +template<> +inline const Slic3r::Polygon& contour(const Slic3r::ExPolygon& sh) +{ + return sh.contour; +} + +template<> +inline void reserve(Slic3r::Polygon& p, size_t vertex_capacity, const PathTag&) +{ + p.points.reserve(vertex_capacity); +} + +template<> +inline void addVertex(Slic3r::Polygon& sh, const PathTag&, const Slic3r::Point &p) +{ + sh.points.emplace_back(p); +} + +#define DISABLE_BOOST_TRANSLATE +template<> +inline void translate(Slic3r::ExPolygon& sh, const Slic3r::Point& offs) +{ + sh.translate(offs); +} + +#define DISABLE_BOOST_ROTATE +template<> +inline void rotate(Slic3r::ExPolygon& sh, const Radians& rads) +{ + sh.rotate(rads); +} + +} // namespace shapelike + +namespace nfp { + +#define DISABLE_BOOST_NFP_MERGE +template<> +inline TMultiShape merge(const TMultiShape& shapes) +{ + return Slic3r::union_ex(shapes); +} + +} // namespace nfp +} // namespace libnest2d + +#define DISABLE_BOOST_CONVEX_HULL + +//#define DISABLE_BOOST_SERIALIZE +//#define DISABLE_BOOST_UNSERIALIZE + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +// All other operators and algorithms are implemented with boost +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // CLIPPER_BACKEND_HPP diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 3095c717dba..7ea43733982 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -128,22 +128,32 @@ template struct ContourType> { using Type = typename ContourType::Type; }; -enum class Orientation { - CLOCKWISE, - COUNTER_CLOCKWISE -}; +enum class Orientation { CLOCKWISE, COUNTER_CLOCKWISE }; template struct OrientationType { // Default Polygon orientation that the library expects - static const Orientation Value = Orientation::CLOCKWISE; + static const constexpr Orientation Value = Orientation::CLOCKWISE; }; -template inline /*constexpr*/ bool is_clockwise() { +template inline constexpr bool is_clockwise() { return OrientationType>::Value == Orientation::CLOCKWISE; } +template +inline const constexpr Orientation OrientationTypeV = + OrientationType>::Value; + +enum class Closure { OPEN, CLOSED }; + +template struct ClosureType { + static const constexpr Closure Value = Closure::CLOSED; +}; + +template +inline const constexpr Closure ClosureTypeV = + ClosureType>::Value; /** * \brief A point pair base class for other point pairs (segment, box, ...). @@ -587,9 +597,9 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) } template -inline void addVertex(S& sh, const PathTag&, Args...args) +inline void addVertex(S& sh, const PathTag&, const TPoint &p) { - sh.emplace_back(std::forward(args)...); + sh.emplace_back(p); } template @@ -841,9 +851,9 @@ template auto rbegin(P& p) -> decltype(_backward(end(p))) return _backward(end(p)); } -template auto rcbegin(const P& p) -> decltype(_backward(end(p))) +template auto rcbegin(const P& p) -> decltype(_backward(cend(p))) { - return _backward(end(p)); + return _backward(cend(p)); } template auto rend(P& p) -> decltype(_backward(begin(p))) @@ -873,16 +883,16 @@ inline void reserve(T& sh, size_t vertex_capacity) { reserve(sh, vertex_capacity, Tag()); } -template -inline void addVertex(S& sh, const PolygonTag&, Args...args) +template +inline void addVertex(S& sh, const PolygonTag&, const TPoint &p) { - addVertex(contour(sh), PathTag(), std::forward(args)...); + addVertex(contour(sh), PathTag(), p); } -template // Tag dispatcher -inline void addVertex(S& sh, Args...args) +template // Tag dispatcher +inline void addVertex(S& sh, const TPoint &p) { - addVertex(sh, Tag(), std::forward(args)...); + addVertex(sh, Tag(), p); } template diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index 29a1ccd047c..d9f94780268 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -28,7 +28,7 @@ inline void buildPolygon(const EdgeList& edgelist, auto& rsh = sl::contour(rpoly); - sl::reserve(rsh, 2*edgelist.size()); + sl::reserve(rsh, 2 * edgelist.size()); // Add the two vertices from the first edge into the final polygon. sl::addVertex(rsh, edgelist.front().first()); @@ -57,7 +57,6 @@ inline void buildPolygon(const EdgeList& edgelist, tmp = std::next(tmp); } - } template @@ -214,15 +213,24 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, // Reserve the needed memory edgelist.reserve(cap); sl::reserve(rsh, static_cast(cap)); + auto add_edge = [&edgelist](const Vertex &v1, const Vertex &v2) { + Edge e{v1, v2}; + if (e.sqlength() > 0) + edgelist.emplace_back(e); + }; { // place all edges from sh into edgelist auto first = sl::cbegin(sh); auto next = std::next(first); while(next != sl::cend(sh)) { - edgelist.emplace_back(*(first), *(next)); + add_edge(*(first), *(next)); + ++first; ++next; } + + if constexpr (ClosureTypeV == Closure::OPEN) + add_edge(*sl::rcbegin(sh), *sl::cbegin(sh)); } { // place all edges from other into edgelist @@ -230,15 +238,19 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, auto next = std::next(first); while(next != sl::cend(other)) { - edgelist.emplace_back(*(next), *(first)); + add_edge(*(next), *(first)); + ++first; ++next; } + + if constexpr (ClosureTypeV == Closure::OPEN) + add_edge(*sl::cbegin(other), *sl::rcbegin(other)); } - std::sort(edgelist.begin(), edgelist.end(), - [](const Edge& e1, const Edge& e2) + std::sort(edgelist.begin(), edgelist.end(), + [](const Edge& e1, const Edge& e2) { - Vertex ax(1, 0); // Unit vector for the X axis + const Vertex ax(1, 0); // Unit vector for the X axis // get cectors from the edges Vertex p1 = e1.second() - e1.first(); @@ -284,12 +296,18 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, // If Ratio is an actual rational type, there is no precision loss auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0]; auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1]; - - return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2; + + if constexpr (is_clockwise()) + return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2; + else + return q[0] < 2 ? pcos1 > pcos2 : pcos1 < pcos2; } // If in different quadrants, compare the quadrant indices only. - return q[0] > q[1]; + if constexpr (is_clockwise()) + return q[0] > q[1]; + else + return q[0] < q[1]; }); __nfp::buildPolygon(edgelist, rsh, top_nfp); diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 9d24a256969..a4cf7dc569f 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -7,6 +7,10 @@ #include #endif +#ifdef LIBNEST2D_GEOMETRIES_libslic3r +#include +#endif + #ifdef LIBNEST2D_OPTIMIZER_nlopt // We include the stock optimizers for local and global optimization #include // Local subplex for NfpPlacer diff --git a/src/libnest2d/include/libnest2d/nester.hpp b/src/libnest2d/include/libnest2d/nester.hpp index 20da9b9a1ed..52c738a4c18 100644 --- a/src/libnest2d/include/libnest2d/nester.hpp +++ b/src/libnest2d/include/libnest2d/nester.hpp @@ -96,7 +96,7 @@ public: * @return The orientation type identifier for the _Item type. */ static BP2D_CONSTEXPR Orientation orientation() { - return OrientationType::Value; + return OrientationType>::Value; } /** @@ -446,44 +446,32 @@ private: } }; +template Sh create_rect(TCoord width, TCoord height) +{ + auto sh = sl::create( + {{0, 0}, {0, height}, {width, height}, {width, 0}}); + + if constexpr (ClosureTypeV == Closure::CLOSED) + sl::addVertex(sh, {0, 0}); + + if constexpr (OrientationTypeV == Orientation::COUNTER_CLOCKWISE) + std::reverse(sl::begin(sh), sl::end(sh)); + + return sh; +} + /** * \brief Subclass of _Item for regular rectangle items. */ -template -class _Rectangle: public _Item { - using _Item::vertex; +template +class _Rectangle: public _Item { + using _Item::vertex; using TO = Orientation; public: - using Unit = TCoord>; + using Unit = TCoord; - template::Value> - inline _Rectangle(Unit width, Unit height, - // disable this ctor if o != CLOCKWISE - enable_if_t< o == TO::CLOCKWISE, int> = 0 ): - _Item( sl::create( { - {0, 0}, - {0, height}, - {width, height}, - {width, 0}, - {0, 0} - } )) - { - } - - template::Value> - inline _Rectangle(Unit width, Unit height, - // disable this ctor if o != COUNTER_CLOCKWISE - enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ): - _Item( sl::create( { - {0, 0}, - {width, 0}, - {width, height}, - {0, height}, - {0, 0} - } )) - { - } + inline _Rectangle(Unit w, Unit h): _Item{create_rect(w, h)} {} inline Unit width() const BP2D_NOEXCEPT { return getX(vertex(2)); diff --git a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp index e1a4ffbd929..a067194dc91 100644 --- a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp @@ -365,44 +365,50 @@ protected: // the additional vertices for maintaning min object distance sl::reserve(rsh, finish-start+4); - /*auto addOthers = [&rsh, finish, start, &item](){ + auto addOthers_ = [&rsh, finish, start, &item](){ for(size_t i = start+1; i < finish; i++) sl::addVertex(rsh, item.vertex(i)); - };*/ + }; - auto reverseAddOthers = [&rsh, finish, start, &item](){ + auto reverseAddOthers_ = [&rsh, finish, start, &item](){ for(auto i = finish-1; i > start; i--) - sl::addVertex(rsh, item.vertex( - static_cast(i))); + sl::addVertex(rsh, item.vertex(static_cast(i))); + }; + + auto addOthers = [&addOthers_, &reverseAddOthers_]() { + if constexpr (!is_clockwise()) + addOthers_(); + else + reverseAddOthers_(); }; // Final polygon construction... - static_assert(OrientationType::Value == - Orientation::CLOCKWISE, - "Counter clockwise toWallPoly() Unimplemented!"); - // Clockwise polygon construction sl::addVertex(rsh, topleft_vertex); - if(dir == Dir::LEFT) reverseAddOthers(); + if(dir == Dir::LEFT) addOthers(); else { - sl::addVertex(rsh, getX(topleft_vertex), 0); - sl::addVertex(rsh, getX(bottomleft_vertex), 0); + sl::addVertex(rsh, {getX(topleft_vertex), 0}); + sl::addVertex(rsh, {getX(bottomleft_vertex), 0}); } sl::addVertex(rsh, bottomleft_vertex); if(dir == Dir::LEFT) { - sl::addVertex(rsh, 0, getY(bottomleft_vertex)); - sl::addVertex(rsh, 0, getY(topleft_vertex)); + sl::addVertex(rsh, {0, getY(bottomleft_vertex)}); + sl::addVertex(rsh, {0, getY(topleft_vertex)}); } - else reverseAddOthers(); + else addOthers(); // Close the polygon - sl::addVertex(rsh, topleft_vertex); + if constexpr (ClosureTypeV == Closure::CLOSED) + sl::addVertex(rsh, topleft_vertex); + + if constexpr (!is_clockwise()) + std::reverse(rsh.begin(), rsh.end()); return ret; } diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 70168c85ab2..47ba7bbdc59 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -344,8 +344,7 @@ inline void correctNfpPosition(nfp::NfpResult& nfp, auto dtouch = touch_sh - touch_other; auto top_other = orbiter.rightmostTopVertex() + dtouch; auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point - //FIXME the explicit type conversion ClipperLib::IntPoint() - shapelike::translate(nfp.first, ClipperLib::IntPoint(dnfp)); + shapelike::translate(nfp.first, dnfp); } template @@ -474,8 +473,7 @@ public: auto bbin = sl::boundingBox(bin); auto d = bbch.center() - bbin.center(); auto chullcpy = chull; - //FIXME the explicit type conversion ClipperLib::IntPoint() - sl::translate(chullcpy, ClipperLib::IntPoint(d)); + sl::translate(chullcpy, d); return sl::isInside(chullcpy, bin) ? -1.0 : 1.0; } diff --git a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp index 16dee513b2a..d6213d0edd1 100644 --- a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp +++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp @@ -19,7 +19,7 @@ #pragma warning(pop) #endif // this should be removed to not confuse the compiler -// #include +// #include "../libnest2d.hpp" namespace bp2d { @@ -30,6 +30,10 @@ using libnest2d::PolygonImpl; using libnest2d::PathImpl; using libnest2d::Orientation; using libnest2d::OrientationType; +using libnest2d::OrientationTypeV; +using libnest2d::ClosureType; +using libnest2d::Closure; +using libnest2d::ClosureTypeV; using libnest2d::getX; using libnest2d::getY; using libnest2d::setX; @@ -213,8 +217,15 @@ struct ToBoostOrienation { static const order_selector Value = counterclockwise; }; -static const bp2d::Orientation RealOrientation = - bp2d::OrientationType::Value; +template struct ToBoostClosure {}; + +template<> struct ToBoostClosure { + static const constexpr closure_selector Value = closure_selector::open; +}; + +template<> struct ToBoostClosure { + static const constexpr closure_selector Value = closure_selector::closed; +}; // Ring implementation ///////////////////////////////////////////////////////// @@ -225,12 +236,13 @@ template<> struct tag { template<> struct point_order { static const order_selector value = - ToBoostOrienation::Value; + ToBoostOrienation>::Value; }; // All our Paths should be closed for the bin packing application template<> struct closure { - static const closure_selector value = closed; + static const constexpr closure_selector value = + ToBoostClosure< bp2d::ClosureTypeV >::Value; }; // Polygon implementation ////////////////////////////////////////////////////// diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 91f35f8456c..d458b03cf1b 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -3,7 +3,7 @@ #include "BoundingBox.hpp" -#include +#include #include #include #include @@ -63,14 +63,13 @@ inline constexpr Eigen::Matrix unscaled( namespace arrangement { using namespace libnest2d; -namespace clppr = ClipperLib; // Get the libnest2d types for clipper backend -using Item = _Item; -using Box = _Box; -using Circle = _Circle; -using Segment = _Segment; -using MultiPolygon = TMultiShape; +using Item = _Item; +using Box = _Box; +using Circle = _Circle; +using Segment = _Segment; +using MultiPolygon = ExPolygons; // Summon the spatial indexing facilities from boost namespace bgi = boost::geometry::index; @@ -127,8 +126,8 @@ template class AutoArranger { public: // Useful type shortcuts... - using Placer = typename placers::_NofitPolyPlacer; - using Selector = selections::_FirstFitSelection; + using Placer = typename placers::_NofitPolyPlacer; + using Selector = selections::_FirstFitSelection; using Packer = _Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; @@ -168,7 +167,7 @@ protected: // as it possibly can be but at the same time, it has to provide // reasonable results. std::tuple - objfunc(const Item &item, const clppr::IntPoint &bincenter) + objfunc(const Item &item, const Point &bincenter) { const double bin_area = m_bin_area; const SpatIndex& spatindex = m_rtree; @@ -220,12 +219,12 @@ protected: switch (compute_case) { case BIG_ITEM: { - const clppr::IntPoint& minc = ibb.minCorner(); // bottom left corner - const clppr::IntPoint& maxc = ibb.maxCorner(); // top right corner + const Point& minc = ibb.minCorner(); // bottom left corner + const Point& maxc = ibb.maxCorner(); // top right corner // top left and bottom right corners - clppr::IntPoint top_left{getX(minc), getY(maxc)}; - clppr::IntPoint bottom_right{getX(maxc), getY(minc)}; + Point top_left{getX(minc), getY(maxc)}; + Point bottom_right{getX(maxc), getY(minc)}; // Now the distance of the gravity center will be calculated to the // five anchor points and the smallest will be chosen. @@ -452,7 +451,7 @@ template<> std::function AutoArranger::get_objfn() // Specialization for a generalized polygon. // Warning: this is unfinished business. It may or may not work. template<> -std::function AutoArranger::get_objfn() +std::function AutoArranger::get_objfn() { auto bincenter = sl::boundingBox(m_bin).center(); return [this, bincenter](const Item &item) { @@ -521,7 +520,7 @@ void _arrange( inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};} inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); } -inline clppr::Polygon to_nestbin(const Polygon &p) { return sl::create(Slic3rMultiPoint_to_ClipperPath(p)); } +inline ExPolygon to_nestbin(const Polygon &p) { return ExPolygon{p}; } inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); } inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); } @@ -568,19 +567,12 @@ static void process_arrangeable(const ArrangePolygon &arrpoly, const Vec2crd &offs = arrpoly.translation; double rotation = arrpoly.rotation; - if (p.is_counter_clockwise()) p.reverse(); - - clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); - // This fixes: // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (clpath.Contour.size() < 3) + if (p.points.size() < 3) return; - auto firstp = clpath.Contour.front(); - clpath.Contour.emplace_back(firstp); - - outp.emplace_back(std::move(clpath)); + outp.emplace_back(std::move(p)); outp.back().rotation(rotation); outp.back().translation({offs.x(), offs.y()}); outp.back().binId(arrpoly.bed_idx); @@ -643,7 +635,7 @@ void arrange(ArrangePolygons & arrangables, _arrange(items, fixeditems, to_nestbin(bed), params, pri, cfn); for(size_t i = 0; i < items.size(); ++i) { - clppr::IntPoint tr = items[i].translation(); + Point tr = items[i].translation(); arrangables[i].translation = {coord_t(tr.x()), coord_t(tr.y())}; arrangables[i].rotation = items[i].rotation(); arrangables[i].bed_idx = items[i].binId(); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 73770bb185a..fcf3c159ece 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -360,6 +360,8 @@ extern std::vector get_extents_vector(const ExPolygons &polygons); extern bool remove_sticks(ExPolygon &poly); extern void keep_largest_contour_only(ExPolygons &polygons); +inline double area(const ExPolygon &poly) { return poly.area(); } + inline double area(const ExPolygons &polys) { double s = 0.; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index dd2d68d46fe..01d4d3decaf 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -72,6 +72,16 @@ public: // Projection of a point onto the polygon. Point point_projection(const Point &point) const; std::vector parameter_by_length() const; + + using iterator = Points::iterator; + using const_iterator = Points::const_iterator; + + inline auto begin() { return points.begin(); } + inline auto begin() const { return points.begin(); } + inline auto end() { return points.end(); } + inline auto end() const { return points.end(); } + inline auto cbegin() const { return points.begin(); } + inline auto cend() const { return points.end(); } }; inline bool operator==(const Polygon &lhs, const Polygon &rhs) { return lhs.points == rhs.points; } @@ -90,6 +100,8 @@ inline double total_length(const Polygons &polylines) { return total; } +inline double area(const Polygon &poly) { return poly.area(); } + inline double area(const Polygons &polys) { double s = 0.; diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index 087903566c9..2243a3c1b54 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -4,7 +4,6 @@ #include #include "libslic3r/ExPolygon.hpp" #include "libslic3r/MTUtils.hpp" -#include // For rasterizing #include @@ -21,10 +20,7 @@ namespace Slic3r { inline const Polygon& contour(const ExPolygon& p) { return p.contour; } -inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } - inline const Polygons& holes(const ExPolygon& p) { return p.holes; } -inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } namespace sla { @@ -77,8 +73,6 @@ protected: double getPx(const Point &p) { return p(0) * m_pxdim_scaled.w_mm; } double getPy(const Point &p) { return p(1) * m_pxdim_scaled.h_mm; } agg::path_storage to_path(const Polygon &poly) { return to_path(poly.points); } - double getPx(const ClipperLib::IntPoint &p) { return p.x() * m_pxdim_scaled.w_mm; } - double getPy(const ClipperLib::IntPoint& p) { return p.y() * m_pxdim_scaled.h_mm; } template agg::path_storage _to_path(const PointVec& v) { @@ -168,7 +162,6 @@ public: } void draw(const ExPolygon &poly) override { _draw(poly); } - void draw(const ClipperLib::Polygon &poly) override { _draw(poly); } EncodedRaster encode(RasterEncoder encoder) const override { diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 9f9f29cd528..bbb83b5a0bf 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -11,8 +11,6 @@ #include #include -namespace ClipperLib { struct Polygon; } - namespace Slic3r { template using uqptr = std::unique_ptr; @@ -92,7 +90,6 @@ public: /// Draw a polygon with holes. virtual void draw(const ExPolygon& poly) = 0; - virtual void draw(const ClipperLib::Polygon& poly) = 0; /// Get the resolution of the raster. virtual Resolution resolution() const = 0; diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 7a4c2906850..0d4eb4547ef 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -14,7 +14,7 @@ #include "ExPolygonCollection.hpp" #include "libslic3r.h" -#include "libnest2d/backends/clipper/geometries.hpp" +#include "libnest2d/backends/libslic3r/geometries.hpp" #include "libnest2d/utils/rotcalipers.hpp" #include @@ -400,7 +400,7 @@ std::vector sample_expolygon(const ExPolygons &expolys, float samples_per void sample_expolygon_boundary(const ExPolygon & expoly, float samples_per_mm, std::vector &out, - std::mt19937 & rng) + std::mt19937 & /*rng*/) { double point_stepping_scaled = scale_(1.f) / samples_per_mm; for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) { @@ -553,8 +553,7 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure // auto bb = get_extents(islands); if (flags & icfIsNew) { - auto chull_ex = ExPolygonCollection{islands}.convex_hull(); - auto chull = Slic3rMultiPoint_to_ClipperPath(chull_ex); + auto chull = ExPolygonCollection{islands}.convex_hull(); auto rotbox = libnest2d::minAreaBoundingBox(chull); Vec2d bbdim = {unscaled(rotbox.width()), unscaled(rotbox.height())}; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index adb80c29ac7..a94eb35fa44 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -9,7 +9,6 @@ #include "Point.hpp" #include "MTUtils.hpp" #include "Zipper.hpp" -#include namespace Slic3r { @@ -483,7 +482,7 @@ public: // The collection of slice records for the current level. std::vector> m_slices; - std::vector m_transformed_slices; + ExPolygons m_transformed_slices; template void transformed_slices(Container&& c) { @@ -507,7 +506,7 @@ public: auto slices() const -> const decltype (m_slices)& { return m_slices; } - const std::vector & transformed_slices() const { + const ExPolygons & transformed_slices() const { return m_transformed_slices; } }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 6058fe192f1..108159b8913 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -16,9 +16,6 @@ #include -// For geometry algorithms with native Clipper types (no copies and conversions) -#include - #include #include "I18N.hpp" @@ -717,55 +714,49 @@ void SLAPrint::Steps::slice_supports(SLAPrintObject &po) { report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); } -using ClipperPoint = ClipperLib::IntPoint; -using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d -using ClipperPolygons = std::vector; +//static ClipperPolygons polyunion(const ClipperPolygons &subjects) +//{ +// ClipperLib::Clipper clipper; -static ClipperPolygons polyunion(const ClipperPolygons &subjects) -{ - ClipperLib::Clipper clipper; +// bool closed = true; - bool closed = true; +// for(auto& path : subjects) { +// clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); +// clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); +// } - for(auto& path : subjects) { - clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); - clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); - } +// auto mode = ClipperLib::pftPositive; - auto mode = ClipperLib::pftPositive; +// return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); +//} - return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); -} +//static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips) +//{ +// ClipperLib::Clipper clipper; -static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips) -{ - ClipperLib::Clipper clipper; +// bool closed = true; - bool closed = true; +// for(auto& path : subjects) { +// clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); +// clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); +// } - for(auto& path : subjects) { - clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); - clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); - } +// for(auto& path : clips) { +// clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); +// clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); +// } - for(auto& path : clips) { - clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); - clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); - } +// auto mode = ClipperLib::pftPositive; - auto mode = ClipperLib::pftPositive; - - return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); -} +// return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); +//} // get polygons for all instances in the object -static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) +static ExPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) { - namespace sl = libnest2d::sl; - if (!record.print_obj()) return {}; - ClipperPolygons polygons; + ExPolygons polygons; auto &input_polygons = record.get_slice(o); auto &instances = record.print_obj()->instances(); bool is_lefthanded = record.print_obj()->is_left_handed(); @@ -776,43 +767,42 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o for (size_t i = 0; i < instances.size(); ++i) { - ClipperPolygon poly; + ExPolygon poly; // We need to reverse if is_lefthanded is true but bool needreverse = is_lefthanded; // should be a move - poly.Contour.reserve(polygon.contour.size() + 1); + poly.contour.points.reserve(polygon.contour.size() + 1); auto& cntr = polygon.contour.points; if(needreverse) for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) - poly.Contour.emplace_back(it->x(), it->y()); + poly.contour.points.emplace_back(it->x(), it->y()); else for(auto& p : cntr) - poly.Contour.emplace_back(p.x(), p.y()); + poly.contour.points.emplace_back(p.x(), p.y()); for(auto& h : polygon.holes) { - poly.Holes.emplace_back(); - auto& hole = poly.Holes.back(); - hole.reserve(h.points.size() + 1); + poly.holes.emplace_back(); + auto& hole = poly.holes.back(); + hole.points.reserve(h.points.size() + 1); if(needreverse) for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) - hole.emplace_back(it->x(), it->y()); + hole.points.emplace_back(it->x(), it->y()); else for(auto& p : h.points) - hole.emplace_back(p.x(), p.y()); + hole.points.emplace_back(p.x(), p.y()); } if(is_lefthanded) { - for(auto& p : poly.Contour) p.x() = -p.x(); - for(auto& h : poly.Holes) for(auto& p : h) p.x() = -p.x(); + for(auto& p : poly.contour) p.x() = -p.x(); + for(auto& h : poly.holes) for(auto& p : h) p.x() = -p.x(); } - sl::rotate(poly, double(instances[i].rotation)); - sl::translate(poly, ClipperPoint{instances[i].shift.x(), - instances[i].shift.y()}); + poly.rotate(double(instances[i].rotation)); + poly.translate(Point{instances[i].shift.x(), instances[i].shift.y()}); polygons.emplace_back(std::move(poly)); } @@ -878,9 +868,6 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { print_statistics.clear(); - // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise - auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); }; - const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%); const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0; const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0; @@ -913,7 +900,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Going to parallel: auto printlayerfn = [this, // functions and read only vars - areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, + area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, // write vars &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, @@ -931,8 +918,8 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Calculation of the consumed material - ClipperPolygons model_polygons; - ClipperPolygons supports_polygons; + ExPolygons model_polygons; + ExPolygons supports_polygons; size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), @@ -954,44 +941,44 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { for(const SliceRecord& record : layer.slices()) { - ClipperPolygons modelslices = get_all_polygons(record, soModel); - for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); + ExPolygons modelslices = get_all_polygons(record, soModel); + for(ExPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); - ClipperPolygons supportslices = get_all_polygons(record, soSupport); - for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); + ExPolygons supportslices = get_all_polygons(record, soSupport); + for(ExPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); } - model_polygons = polyunion(model_polygons); + model_polygons = union_ex(model_polygons); double layer_model_area = 0; - for (const ClipperPolygon& polygon : model_polygons) - layer_model_area += areafn(polygon); + for (const ExPolygon& polygon : model_polygons) + layer_model_area += area(polygon); if (layer_model_area < 0 || layer_model_area > 0) { Lock lck(mutex); models_volume += layer_model_area * l_height; } if(!supports_polygons.empty()) { - if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons); - else supports_polygons = polydiff(supports_polygons, model_polygons); + if(model_polygons.empty()) supports_polygons = union_ex(supports_polygons); + else supports_polygons = diff_ex(supports_polygons, model_polygons); // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType } double layer_support_area = 0; - for (const ClipperPolygon& polygon : supports_polygons) - layer_support_area += areafn(polygon); + for (const ExPolygon& polygon : supports_polygons) + layer_support_area += area(polygon); if (layer_support_area < 0 || layer_support_area > 0) { Lock lck(mutex); supports_volume += layer_support_area * l_height; } // Here we can save the expensively calculated polygons for printing - ClipperPolygons trslices; + ExPolygons trslices; trslices.reserve(model_polygons.size() + supports_polygons.size()); - for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); - for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); + for(ExPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); + for(ExPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); - layer.transformed_slices(polyunion(trslices)); + layer.transformed_slices(union_ex(trslices)); // Calculation of the slow and fast layers to the future controlling those values on FW @@ -1074,7 +1061,7 @@ void SLAPrint::Steps::rasterize() PrintLayer& printlayer = m_print->m_printer_input[idx]; if(canceled()) return; - for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) + for (const ExPolygon& poly : printlayer.transformed_slices()) raster.draw(poly); // Status indication guarded with the spinlock diff --git a/tests/libnest2d/CMakeLists.txt b/tests/libnest2d/CMakeLists.txt index d4f684dd3ca..bcb7594520f 100644 --- a/tests/libnest2d/CMakeLists.txt +++ b/tests/libnest2d/CMakeLists.txt @@ -4,4 +4,4 @@ target_link_libraries(${_TEST_NAME}_tests test_common libnest2d ) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") -add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS}) +add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]") diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index 11fdc6e9cbd..1cec8dcba81 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -44,12 +44,74 @@ struct NfpImpl } } +namespace { +using namespace libnest2d; + +template +void exportSVG(const char *loc, It from, It to) { + + static const char* svg_header = + R"raw( + + +)raw"; + + // for(auto r : result) { + std::fstream out(loc, std::fstream::out); + if(out.is_open()) { + out << svg_header; + // Item rbin( RectangleItem(bin.width(), bin.height()) ); + // for(unsigned j = 0; j < rbin.vertexCount(); j++) { + // auto v = rbin.vertex(j); + // setY(v, -getY(v)/SCALE + 500 ); + // setX(v, getX(v)/SCALE); + // rbin.setVertex(j, v); + // } + // out << shapelike::serialize(rbin.rawShape()) << std::endl; + for(auto it = from; it != to; ++it) { + const Item &itm = *it; + Item tsh(itm.transformedShape()); + for(unsigned j = 0; j < tsh.vertexCount(); j++) { + auto v = tsh.vertex(j); + setY(v, -getY(v)/SCALE + 500); + setX(v, getX(v)/SCALE); + tsh.setVertex(j, v); + } + out << shapelike::serialize(tsh.rawShape()) << std::endl; + } + out << "\n" << std::endl; + } + out.close(); + + // i++; + // } +} + +template +void exportSVG(std::vector>& result, int idx = 0) { + exportSVG((std::string("out") + std::to_string(idx) + ".svg").c_str(), + result.begin(), result.end()); +} +} + static std::vector& prusaParts() { - static std::vector ret; + using namespace libnest2d; + + static std::vector ret; if(ret.empty()) { ret.reserve(PRINTER_PART_POLYGONS.size()); - for(auto& inp : PRINTER_PART_POLYGONS) ret.emplace_back(inp); + for(auto& inp : PRINTER_PART_POLYGONS) { + auto inp_cpy = inp; + + if (ClosureTypeV == Closure::OPEN) + inp_cpy.points.pop_back(); + + if constexpr (!libnest2d::is_clockwise()) + std::reverse(inp_cpy.begin(), inp_cpy.end()); + + ret.emplace_back(inp_cpy); + } } return ret; @@ -140,15 +202,15 @@ TEST_CASE("boundingCircle", "[Geometry]") { PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; Circle c = boundingCircle(p); - REQUIRE(c.center().x() == 0); - REQUIRE(c.center().y() == 0); + REQUIRE(getX(c.center()) == 0); + REQUIRE(getY(c.center()) == 0); REQUIRE(c.radius() == Approx(10)); shapelike::translate(p, PointImpl{10, 10}); c = boundingCircle(p); - REQUIRE(c.center().x() == 10); - REQUIRE(c.center().y() == 10); + REQUIRE(getX(c.center()) == 10); + REQUIRE(getY(c.center()) == 10); REQUIRE(c.radius() == Approx(10)); auto parts = prusaParts(); @@ -243,7 +305,7 @@ TEST_CASE("Area", "[Geometry]") { {61, 97} }; - REQUIRE(shapelike::area(item.transformedShape()) > 0 ); + REQUIRE(std::abs(shapelike::area(item.transformedShape())) > 0 ); } TEST_CASE("IsPointInsidePolygon", "[Geometry]") { @@ -296,30 +358,36 @@ TEST_CASE("LeftAndDownPolygon", "[Geometry]") Box bin(100, 100); BottomLeftPlacer placer(bin); - Item item = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20}, {42, 20}, - {35, 35}, {35, 55}, {40, 75}, {70, 75}}; + PathImpl pitem = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20}, + {42, 20}, {35, 35}, {35, 55}, {40, 75}}; - Item leftControl = { {40, 75}, - {35, 55}, - {35, 35}, - {42, 20}, - {0, 20}, - {0, 75}, - {40, 75}}; + PathImpl pleftControl = {{40, 75}, {35, 55}, {35, 35}, + {42, 20}, {0, 20}, {0, 75}}; - Item downControl = {{88, 60}, - {88, 0}, - {35, 0}, - {35, 35}, - {42, 20}, - {80, 20}, - {60, 30}, - {65, 50}, - {88, 60}}; + PathImpl pdownControl = {{88, 60}, {88, 0}, {35, 0}, {35, 35}, + {42, 20}, {80, 20}, {60, 30}, {65, 50}}; + if constexpr (!is_clockwise()) { + std::reverse(sl::begin(pitem), sl::end(pitem)); + std::reverse(sl::begin(pleftControl), sl::end(pleftControl)); + std::reverse(sl::begin(pdownControl), sl::end(pdownControl)); + } + + if constexpr (ClosureTypeV == Closure::CLOSED) { + sl::addVertex(pitem, sl::front(pitem)); + sl::addVertex(pleftControl, sl::front(pleftControl)); + sl::addVertex(pdownControl, sl::front(pdownControl)); + } + + Item item{pitem}, leftControl{pleftControl}, downControl{pdownControl}; Item leftp(placer.leftPoly(item)); - REQUIRE(shapelike::isValid(leftp.rawShape()).first); + auto valid = sl::isValid(leftp.rawShape()); + + std::vector> to_export{ leftp, leftControl }; + exportSVG<1>("leftp.svg", to_export.begin(), to_export.end()); + + REQUIRE(valid.first); REQUIRE(leftp.vertexCount() == leftControl.vertexCount()); for(unsigned long i = 0; i < leftControl.vertexCount(); i++) { @@ -338,7 +406,7 @@ TEST_CASE("LeftAndDownPolygon", "[Geometry]") } } -TEST_CASE("ArrangeRectanglesTight", "[Nesting]") +TEST_CASE("ArrangeRectanglesTight", "[Nesting][NotWorking]") { using namespace libnest2d; @@ -390,6 +458,8 @@ TEST_CASE("ArrangeRectanglesTight", "[Nesting]") // check for no intersections, no containment: + // exportSVG<1>("arrangeRectanglesTight.svg", rects.begin(), rects.end()); + bool valid = true; for(Item& r1 : rects) { for(Item& r2 : rects) { @@ -470,57 +540,7 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]") } -namespace { -using namespace libnest2d; - -template -void exportSVG(const char *loc, It from, It to) { - - static const char* svg_header = -R"raw( - - -)raw"; - - // for(auto r : result) { - std::fstream out(loc, std::fstream::out); - if(out.is_open()) { - out << svg_header; -// Item rbin( RectangleItem(bin.width(), bin.height()) ); -// for(unsigned j = 0; j < rbin.vertexCount(); j++) { -// auto v = rbin.vertex(j); -// setY(v, -getY(v)/SCALE + 500 ); -// setX(v, getX(v)/SCALE); -// rbin.setVertex(j, v); -// } -// out << shapelike::serialize(rbin.rawShape()) << std::endl; - for(auto it = from; it != to; ++it) { - const Item &itm = *it; - Item tsh(itm.transformedShape()); - for(unsigned j = 0; j < tsh.vertexCount(); j++) { - auto v = tsh.vertex(j); - setY(v, -getY(v)/SCALE + 500); - setX(v, getX(v)/SCALE); - tsh.setVertex(j, v); - } - out << shapelike::serialize(tsh.rawShape()) << std::endl; - } - out << "\n" << std::endl; - } - out.close(); - - // i++; - // } -} - -template -void exportSVG(std::vector>& result, int idx = 0) { - exportSVG((std::string("out") + std::to_string(idx) + ".svg").c_str(), - result.begin(), result.end()); -} -} - -TEST_CASE("BottomLeftStressTest", "[Geometry]") { +TEST_CASE("BottomLeftStressTest", "[Geometry][NotWorking]") { using namespace libnest2d; const Coord SCALE = 1000000; @@ -563,7 +583,7 @@ TEST_CASE("BottomLeftStressTest", "[Geometry]") { TEST_CASE("convexHull", "[Geometry]") { using namespace libnest2d; - ClipperLib::Path poly = PRINTER_PART_POLYGONS[0]; + PathImpl poly = PRINTER_PART_POLYGONS[0]; auto chull = sl::convexHull(poly); @@ -597,7 +617,7 @@ TEST_CASE("PrusaPartsShouldFitIntoTwoBins", "[Nesting]") { })); // Gather the items into piles of arranged polygons... - using Pile = TMultiShape; + using Pile = TMultiShape; std::vector piles(bins); for (auto &itm : input) @@ -609,6 +629,20 @@ TEST_CASE("PrusaPartsShouldFitIntoTwoBins", "[Nesting]") { auto bb = sl::boundingBox(pile); REQUIRE(sl::isInside(bb, bin)); } + + // Check the area of merged pile vs the sum of area of all the parts + // They should match, otherwise there is an overlap which should not happen. + for (auto &pile : piles) { + double area_sum = 0.; + + for (auto &obj : pile) + area_sum += sl::area(obj); + + auto pile_m = nfp::merge(pile); + double area_merge = sl::area(pile_m); + + REQUIRE(area_sum == Approx(area_merge)); + } } TEST_CASE("EmptyItemShouldBeUntouched", "[Nesting]") { @@ -616,7 +650,7 @@ TEST_CASE("EmptyItemShouldBeUntouched", "[Nesting]") { std::vector items; items.emplace_back(Item{}); // Emplace empty item - items.emplace_back(Item{ { 0, 0} , { 200, 0 }, { 0, 0 } }); // Emplace zero area item + items.emplace_back(Item{ {0, 200} }); // Emplace zero area item size_t bins = libnest2d::nest(items, bin); @@ -661,12 +695,12 @@ TEST_CASE("Items can be preloaded", "[Nesting]") { REQUIRE(bins == 1); REQUIRE(fixed_rect.binId() == 0); - REQUIRE(fixed_rect.translation().x() == bin.center().x()); - REQUIRE(fixed_rect.translation().y() == bin.center().y()); + REQUIRE(getX(fixed_rect.translation()) == getX(bin.center())); + REQUIRE(getY(fixed_rect.translation()) == getY(bin.center())); REQUIRE(movable_rect.binId() == 0); - REQUIRE(movable_rect.translation().x() != bin.center().x()); - REQUIRE(movable_rect.translation().y() != bin.center().y()); + REQUIRE(getX(movable_rect.translation()) != getX(bin.center())); + REQUIRE(getY(movable_rect.translation()) != getY(bin.center())); } SECTION("Preloaded Item should not affect free bins") { @@ -677,14 +711,14 @@ TEST_CASE("Items can be preloaded", "[Nesting]") { REQUIRE(bins == 2); REQUIRE(fixed_rect.binId() == 1); - REQUIRE(fixed_rect.translation().x() == bin.center().x()); - REQUIRE(fixed_rect.translation().y() == bin.center().y()); + REQUIRE(getX(fixed_rect.translation()) == getX(bin.center())); + REQUIRE(getY(fixed_rect.translation()) == getY(bin.center())); REQUIRE(movable_rect.binId() == 0); auto bb = movable_rect.boundingBox(); - REQUIRE(bb.center().x() == bin.center().x()); - REQUIRE(bb.center().y() == bin.center().y()); + REQUIRE(getX(bb.center()) == getX(bin.center())); + REQUIRE(getY(bb.center()) == getY(bin.center())); } } @@ -700,15 +734,13 @@ std::vector nfp_testdata = { { {80, 50}, {100, 70}, - {120, 50}, - {80, 50} + {120, 50} }, { {10, 10}, {10, 40}, {40, 40}, - {40, 10}, - {10, 10} + {40, 10} } }, { @@ -718,15 +750,13 @@ std::vector nfp_testdata = { {80, 90}, {120, 90}, {140, 70}, - {120, 50}, - {80, 50} + {120, 50} }, { {10, 10}, {10, 40}, {40, 40}, - {40, 10}, - {10, 10} + {40, 10} } }, { @@ -738,15 +768,13 @@ std::vector nfp_testdata = { {30, 40}, {40, 40}, {50, 30}, - {50, 20}, - {40, 10} + {50, 20} }, { {80, 0}, {80, 30}, {110, 30}, - {110, 0}, - {80, 0} + {110, 0} } }, { @@ -766,9 +794,8 @@ std::vector nfp_testdata = { {122, 97}, {120, 98}, {118, 101}, - {117, 103}, - {117, 107} - }, + {117, 103} + }, { {102, 116}, {111, 126}, @@ -777,9 +804,8 @@ std::vector nfp_testdata = { {148, 100}, {148, 85}, {147, 84}, - {102, 84}, - {102, 116}, - } + {102, 84} + } }, { { @@ -793,9 +819,8 @@ std::vector nfp_testdata = { {139, 68}, {111, 68}, {108, 70}, - {99, 102}, - {99, 122}, - }, + {99, 102} + }, { {107, 124}, {128, 125}, @@ -810,9 +835,8 @@ std::vector nfp_testdata = { {136, 86}, {134, 85}, {108, 85}, - {107, 86}, - {107, 124}, - } + {107, 86} + } }, { { @@ -825,9 +849,8 @@ std::vector nfp_testdata = { {156, 66}, {133, 57}, {132, 57}, - {91, 98}, - {91, 100}, - }, + {91, 98} + }, { {101, 90}, {103, 98}, @@ -843,9 +866,8 @@ std::vector nfp_testdata = { {145, 84}, {105, 84}, {102, 87}, - {101, 89}, - {101, 90}, - } + {101, 89} + } } }; @@ -860,10 +882,9 @@ std::vector nfp_testdata = { {533659, 157607}, {538669, 160091}, {537178, 142155}, - {534959, 143386}, - {533726, 142141}, - } - }, + {534959, 143386} + } + }, { { {118305, 11603}, @@ -884,8 +905,7 @@ std::vector nfp_testdata = { {209315, 17080}, {205326, 17080}, {203334, 13629}, - {204493, 11616}, - {118305, 11603}, + {204493, 11616} } }, } @@ -957,6 +977,14 @@ void testNfp(const std::vector& testdata) { for(auto& td : testdata) { auto orbiter = td.orbiter; auto stationary = td.stationary; + if (!libnest2d::is_clockwise()) { + auto porb = orbiter.rawShape(); + auto pstat = stationary.rawShape(); + std::reverse(sl::begin(porb), sl::end(porb)); + std::reverse(sl::begin(pstat), sl::end(pstat)); + orbiter = Item{porb}; + stationary = Item{pstat}; + } onetest(orbiter, stationary, tidx++); } @@ -964,6 +992,14 @@ void testNfp(const std::vector& testdata) { for(auto& td : testdata) { auto orbiter = td.stationary; auto stationary = td.orbiter; + if (!libnest2d::is_clockwise()) { + auto porb = orbiter.rawShape(); + auto pstat = stationary.rawShape(); + std::reverse(sl::begin(porb), sl::end(porb)); + std::reverse(sl::begin(pstat), sl::end(pstat)); + orbiter = Item{porb}; + stationary = Item{pstat}; + } onetest(orbiter, stationary, tidx++); } } @@ -1073,7 +1109,7 @@ using Ratio = boost::rational; TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") { long double err_epsilon = 500e6l; - for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { + for(PathImpl rinput : PRINTER_PART_POLYGONS) { PolygonImpl poly(rinput); long double arearef = refMinAreaBox(poly); @@ -1085,8 +1121,8 @@ TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") { REQUIRE(succ); } - for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) { - rinput.pop_back(); + for(PathImpl rinput : STEGOSAUR_POLYGONS) { +// rinput.pop_back(); std::reverse(rinput.begin(), rinput.end()); PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); From ab405b5b65f9e78008e1719b429a66587835153a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Apr 2021 15:50:46 +0200 Subject: [PATCH 119/154] Eliminate warnings caused by changes to aid new libslic3r backend --- src/libslic3r/Execution/Execution.hpp | 4 --- src/libslic3r/Fill/FillBase.cpp | 6 +--- src/libslic3r/Line.hpp | 44 ++++++++++++++++++++++----- src/libslic3r/MTUtils.hpp | 4 +-- src/libslic3r/libslic3r.h | 4 +++ 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Execution/Execution.hpp b/src/libslic3r/Execution/Execution.hpp index e4bad9f2370..62e49cfeb36 100644 --- a/src/libslic3r/Execution/Execution.hpp +++ b/src/libslic3r/Execution/Execution.hpp @@ -10,10 +10,6 @@ namespace Slic3r { -// Borrowed from C++20 -template -using remove_cvref_t = std::remove_reference_t>; - // Override for valid execution policies template struct IsExecutionPolicy_ : public std::false_type {}; diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 6d1d94ff8cd..a41a11cb79e 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -595,7 +595,6 @@ static inline bool line_rounded_thick_segment_collision( // Very short line vector. Just test whether the center point is inside the offset line. Vec2d lpt = 0.5 * (line_a + line_b); if (segment_l > SCALED_EPSILON) { - struct Linef { Vec2d a, b; }; intersects = line_alg::distance_to_squared(Linef{ segment_a, segment_b }, lpt) < offset2; } else intersects = (0.5 * (segment_a + segment_b) - lpt).squaredNorm() < offset2; @@ -1196,8 +1195,6 @@ static inline void mark_boundary_segments_overlapping_infill( // Spacing (width) of the infill lines. const double spacing) { - struct Linef { Vec2d a; Vec2d b; }; - for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { const Points &contour = graph.boundary[cp.contour_idx]; const std::vector &contour_params = graph.boundary_params[cp.contour_idx]; @@ -2003,9 +2000,8 @@ static double evaluate_support_arch_cost(const Polyline &pl) double dmax = 0; // Maximum distance in Y axis out of the (ymin, ymax) band and from the (front, back) line. - struct Linef { Vec2d a, b; }; Linef line { front.cast(), back.cast() }; - for (const Point pt : pl.points) + for (const Point &pt : pl.points) dmax = std::max(std::max(dmax, line_alg::distance_to(line, Vec2d(pt.cast()))), std::max(pt.y() - ymax, ymin - pt.y())); return dmax; } diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 72532b4e33d..b62775bfe4d 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -4,6 +4,8 @@ #include "libslic3r.h" #include "Point.hpp" +#include + namespace Slic3r { class BoundingBox; @@ -20,12 +22,28 @@ Linef3 transform(const Linef3& line, const Transform3d& t); namespace line_alg { +template struct Traits { + static constexpr int Dim = L::Dim; + using Scalar = typename L::Scalar; + + static Vec& get_a(L &l) { return l.a; } + static Vec& get_b(L &l) { return l.b; } + static const Vec& get_a(const L &l) { return l.a; } + static const Vec& get_b(const L &l) { return l.b; } +}; + +template const constexpr int Dim = Traits>::Dim; +template using Scalar = typename Traits>::Scalar; + +template auto get_a(L &&l) { return Traits>::get_a(l); } +template auto get_b(L &&l) { return Traits>::get_b(l); } + // Distance to the closest point of line. -template -double distance_to_squared(const L &line, const Vec &point) +template +double distance_to_squared(const L &line, const Vec, Scalar> &point) { - const Vec v = (line.b - line.a).template cast(); - const Vec va = (point - line.a).template cast(); + const Vec, double> v = (get_b(line) - get_a(line)).template cast(); + const Vec, double> va = (point - get_a(line)).template cast(); const double l2 = v.squaredNorm(); // avoid a sqrt if (l2 == 0.0) // a == b case @@ -35,12 +53,12 @@ double distance_to_squared(const L &line, const Vec &point) // It falls where t = [(this-a) . (b-a)] / |b-a|^2 const double t = va.dot(v) / l2; if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment - else if (t > 1.0) return (point - line.b).template cast().squaredNorm(); // beyond the 'b' end of the segment + else if (t > 1.0) return (point - get_b(line)).template cast().squaredNorm(); // beyond the 'b' end of the segment return (t * v - va).squaredNorm(); } -template -double distance_to(const L &line, const Vec &point) +template +double distance_to(const L &line, const Vec, Scalar> &point) { return std::sqrt(distance_to_squared(line, point)); } @@ -84,6 +102,9 @@ public: Point a; Point b; + + static const constexpr int Dim = 2; + using Scalar = Point::Scalar; }; class ThickLine : public Line @@ -107,6 +128,9 @@ public: Vec3crd a; Vec3crd b; + + static const constexpr int Dim = 3; + using Scalar = Vec3crd::Scalar; }; class Linef @@ -117,6 +141,9 @@ public: Vec2d a; Vec2d b; + + static const constexpr int Dim = 2; + using Scalar = Vec2d::Scalar; }; class Linef3 @@ -133,6 +160,9 @@ public: Vec3d a; Vec3d b; + + static const constexpr int Dim = 3; + using Scalar = Vec3d::Scalar; }; BoundingBox get_extents(const Lines &lines); diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 7b903f66c8f..9e77aa90af6 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -106,8 +106,8 @@ template bool all_of(const C &container) }); } -template -using remove_cvref_t = std::remove_reference_t>; +//template +//using remove_cvref_t = std::remove_reference_t>; /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html template> diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index efdecbac8fc..2b0c94161da 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -308,6 +308,10 @@ IntegerOnly> reserve_vector(I capacity) return ret; } +// Borrowed from C++20 +template +using remove_cvref_t = std::remove_cv_t>; + } // namespace Slic3r #endif From 8af3bd96a31aadfe026a02e4badc2051e370f618 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 14 Apr 2021 08:51:54 +0200 Subject: [PATCH 120/154] Fix integer overflows in libnest2d tests --- tests/libnest2d/libnest2d_tests_main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index 1cec8dcba81..181f130e556 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -1207,7 +1207,7 @@ TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") pconfig.object_function = [&pile_box](const Item &item) -> double { Box b = sl::boundingBox(item.boundingBox(), pile_box); - double area = b.area() / (W * W); + double area = b.area() / (double(W) * W); return -area; }; @@ -1223,5 +1223,5 @@ TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") // Here the result shall be a stairway of boxes REQUIRE(pile.size() == N); - REQUIRE(bb.area() == N * N * W * W); + REQUIRE(bb.area() == double(N) * N * W * W); } From 080f47e64b3ad3ba9fe826c11a69d812010b5436 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Apr 2021 16:29:12 +0200 Subject: [PATCH 121/154] Write hollow flag to SL1 files if any object is hollowed. --- src/libslic3r/Format/SL1.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 64cb8b81547..554e49b5a59 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -371,6 +371,13 @@ void fill_iniconf(ConfMap &m, const SLAPrint &print) m["numSlow"] = std::to_string(stats.slow_layers_count); m["numFast"] = std::to_string(stats.fast_layers_count); m["printTime"] = std::to_string(stats.estimated_print_time); + + bool hollow_en = false; + auto it = print.objects().begin(); + while (!hollow_en && it != print.objects().end()) + hollow_en = hollow_en || (*it++)->config().hollowing_enable.getBool(); + + m["hollow"] = hollow_en ? "1" : "0"; m["action"] = "print"; } From a48acc55814d8c3f0845c9a46322793bebf297c3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Apr 2021 16:49:11 +0200 Subject: [PATCH 122/154] Minor code refinements --- src/libslic3r/Format/SL1.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 554e49b5a59..4038cb04675 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -248,7 +248,7 @@ std::vector extract_slices_from_sla_archive( { double incr, val, prev; bool stop = false; - tbb::spin_mutex mutex; + tbb::spin_mutex mutex = {}; } st {100. / slices.size(), 0., 0.}; tbb::parallel_for(size_t(0), arch.images.size(), @@ -375,7 +375,7 @@ void fill_iniconf(ConfMap &m, const SLAPrint &print) bool hollow_en = false; auto it = print.objects().begin(); while (!hollow_en && it != print.objects().end()) - hollow_en = hollow_en || (*it++)->config().hollowing_enable.getBool(); + hollow_en = (*it++)->config().hollowing_enable; m["hollow"] = hollow_en ? "1" : "0"; From c5c73f425736088a5d6de73c31a8bf1682ce47f4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Apr 2021 17:20:20 +0200 Subject: [PATCH 123/154] Use new libnest backend for MinAreaBoundingBox wrapper --- .../backends/libslic3r/geometries.hpp | 11 +++ src/libslic3r/MinAreaBoundingBox.cpp | 79 ++++--------------- src/libslic3r/MinAreaBoundingBox.hpp | 8 +- src/libslic3r/SLA/SupportPointGenerator.cpp | 6 +- 4 files changed, 32 insertions(+), 72 deletions(-) diff --git a/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp b/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp index 08439a63e54..49852318065 100644 --- a/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp @@ -72,6 +72,7 @@ template<> struct ShapeTag { using Type = PointTag; }; template<> struct ShapeTag> { using Type = PathTag; }; template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PathTag; }; template<> struct ShapeTag { using Type = PolygonTag; }; template<> struct ShapeTag { using Type = MultiPolygonTag; }; @@ -104,11 +105,21 @@ struct OrientationType { static const constexpr Orientation Value = Orientation::COUNTER_CLOCKWISE; }; +template<> +struct OrientationType { + static const constexpr Orientation Value = Orientation::COUNTER_CLOCKWISE; +}; + template<> struct ClosureType { static const constexpr Closure Value = Closure::OPEN; }; +template<> +struct ClosureType { + static const constexpr Closure Value = Closure::OPEN; +}; + template<> struct MultiShape { using Type = Slic3r::ExPolygons; }; template<> struct ContourType { using Type = Slic3r::Polygon; }; diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp index 15c04517d08..51fd8a45e76 100644 --- a/src/libslic3r/MinAreaBoundingBox.cpp +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -14,55 +14,9 @@ #include #endif -#include +#include #include -namespace libnest2d { - -template<> struct PointType { using Type = Slic3r::Point; }; -template<> struct CoordType { using Type = coord_t; }; -template<> struct ShapeTag { using Type = PolygonTag; }; -template<> struct ShapeTag { using Type = PolygonTag; }; -template<> struct ShapeTag { using Type = PathTag; }; -template<> struct ShapeTag { using Type = PointTag; }; -template<> struct ContourType { using Type = Slic3r::Points; }; -template<> struct ContourType { using Type = Slic3r::Points; }; - -namespace pointlike { - -template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); } -template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); } -template<> inline coord_t& x(Slic3r::Point& p) { return p.x(); } -template<> inline coord_t& y(Slic3r::Point& p) { return p.y(); } - -} // pointlike - -namespace shapelike { -template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; } -template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; } -template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; } -template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; } - -template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();} -template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.cbegin(); } -template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();} -template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); } - -template<> inline Slic3r::ExPolygon create(Slic3r::Points&& contour) -{ - Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour); - return expoly; -} - -template<> inline Slic3r::Polygon create(Slic3r::Points&& contour) -{ - Slic3r::Polygon poly; poly.points.swap(contour); - return poly; -} - -} // shapelike -} // libnest2d - namespace Slic3r { // Used as compute type. @@ -74,13 +28,22 @@ using Rational = boost::rational; using Rational = boost::rational<__int128>; #endif +template +libnest2d::RotatedBox minAreaBoundigBox_( + const P &p, MinAreaBoundigBox::PolygonLevel lvl) +{ + P chull = lvl == MinAreaBoundigBox::pcConvex ? + p : + libnest2d::sl::convexHull(p); + + libnest2d::removeCollinearPoints(chull); + + return libnest2d::minAreaBoundingBox(chull); +} + MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc) { - const Polygon &chull = pc == pcConvex ? p : - libnest2d::sl::convexHull(p); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); + libnest2d::RotatedBox box = minAreaBoundigBox_(p, pc); m_right = libnest2d::cast(box.right_extent()); m_bottom = libnest2d::cast(box.bottom_extent()); @@ -89,11 +52,7 @@ MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc) MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc) { - const ExPolygon &chull = pc == pcConvex ? p : - libnest2d::sl::convexHull(p); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); + libnest2d::RotatedBox box = minAreaBoundigBox_(p, pc); m_right = libnest2d::cast(box.right_extent()); m_bottom = libnest2d::cast(box.bottom_extent()); @@ -102,11 +61,7 @@ MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc) MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc) { - const Points &chull = pc == pcConvex ? pts : - libnest2d::sl::convexHull(pts); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); + libnest2d::RotatedBox box = minAreaBoundigBox_(pts, pc); m_right = libnest2d::cast(box.right_extent()); m_bottom = libnest2d::cast(box.bottom_extent()); diff --git a/src/libslic3r/MinAreaBoundingBox.hpp b/src/libslic3r/MinAreaBoundingBox.hpp index 30d0e9799d4..242fc96111f 100644 --- a/src/libslic3r/MinAreaBoundingBox.hpp +++ b/src/libslic3r/MinAreaBoundingBox.hpp @@ -26,12 +26,8 @@ public: }; // Constructors with various types of geometry data used in Slic3r. - // If the convexity is known apriory, pcConvex can be used to skip - // convex hull calculation. It is very important that the input polygons - // do NOT have any collinear points (except for the first and the last - // vertex being the same -- meaning a closed polygon for boost) - // To make sure this constraint is satisfied, you can call - // remove_collinear_points on the input polygon before handing over here) + // If the convexity is known apriory, pcConvex can be used to skip + // convex hull calculation. explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple); explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple); explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple); diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 0d4eb4547ef..5ef4eb00163 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -12,11 +12,9 @@ #include "ClipperUtils.hpp" #include "Tesselate.hpp" #include "ExPolygonCollection.hpp" +#include "MinAreaBoundingBox.hpp" #include "libslic3r.h" -#include "libnest2d/backends/libslic3r/geometries.hpp" -#include "libnest2d/utils/rotcalipers.hpp" - #include #include @@ -554,7 +552,7 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure if (flags & icfIsNew) { auto chull = ExPolygonCollection{islands}.convex_hull(); - auto rotbox = libnest2d::minAreaBoundingBox(chull); + auto rotbox = MinAreaBoundigBox{chull, MinAreaBoundigBox::pcConvex}; Vec2d bbdim = {unscaled(rotbox.width()), unscaled(rotbox.height())}; if (bbdim.x() > bbdim.y()) std::swap(bbdim.x(), bbdim.y()); From e912da36ebc5e8e87796bf417758e0b2d3f8378a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 21 Apr 2021 20:15:49 +0200 Subject: [PATCH 124/154] Convincing ClipperLib to use Slic3r's own Point type internally. --- CMakeLists.txt | 2 - src/clipper/CMakeLists.txt | 5 +- src/clipper/clipper.cpp | 26 ++++--- src/clipper/clipper.hpp | 77 ++++++++----------- src/clipper/clipper_z.cpp | 2 +- src/clipper/clipper_z.hpp | 6 +- .../backends/libslic3r/geometries.hpp | 4 +- src/libslic3r/Arrange.cpp | 4 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/ClipperUtils.hpp | 6 +- src/libslic3r/Geometry.hpp | 14 ++-- src/libslic3r/pchheader.hpp | 4 +- tests/libnest2d/libnest2d_tests_main.cpp | 4 +- xs/xsp/Clipper.xsp | 1 - 14 files changed, 74 insertions(+), 83 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a5fc83870d..c6f29515017 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,8 +268,6 @@ set(LIBDIR_BIN ${CMAKE_CURRENT_BINARY_DIR}/src) include_directories(${LIBDIR}) # For generated header files include_directories(${LIBDIR_BIN}/platform) -# For libslic3r.h -include_directories(${LIBDIR}/clipper) if(WIN32) add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) diff --git a/src/clipper/CMakeLists.txt b/src/clipper/CMakeLists.txt index 8a4e9285268..0362a4d849e 100644 --- a/src/clipper/CMakeLists.txt +++ b/src/clipper/CMakeLists.txt @@ -2,8 +2,9 @@ project(clipper) cmake_minimum_required(VERSION 2.6) add_library(clipper STATIC - clipper.cpp - clipper.hpp +# We are using ClipperLib compiled as part of the libslic3r project using Slic3r::Point as its base type. +# clipper.cpp +# clipper.hpp clipper_z.cpp clipper_z.hpp ) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 9f168100748..06c91bf3a84 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -61,11 +61,15 @@ #define CLIPPERLIB_PROFILE_BLOCK(name) #endif -#ifdef use_xyz +#ifdef CLIPPERLIB_NAMESPACE_PREFIX +namespace CLIPPERLIB_NAMESPACE_PREFIX { +#endif // CLIPPERLIB_NAMESPACE_PREFIX + +#ifdef CLIPPERLIB_USE_XYZ namespace ClipperLib_Z { -#else /* use_xyz */ +#else /* CLIPPERLIB_USE_XYZ */ namespace ClipperLib { -#endif /* use_xyz */ +#endif /* CLIPPERLIB_USE_XYZ */ static double const pi = 3.141592653589793238; static double const two_pi = pi *2; @@ -335,7 +339,7 @@ inline cInt TopX(TEdge &edge, const cInt currentY) void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) { -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ ip.z() = 0; #endif @@ -467,7 +471,7 @@ inline void ReverseHorizontal(TEdge &e) //progression of the bounds - ie so their xbots will align with the //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] std::swap(e.Top.x(), e.Bot.x()); -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ std::swap(e.Top.z(), e.Bot.z()); #endif } @@ -1073,7 +1077,7 @@ Clipper::Clipper(int initOptions) : m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); m_HasOpenPaths = false; -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ m_ZFill = 0; #endif } @@ -1637,7 +1641,7 @@ void Clipper::DeleteFromSEL(TEdge *e) } //------------------------------------------------------------------------------ -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) { if (pt.z() != 0 || !m_ZFill) return; @@ -1655,7 +1659,7 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) bool e1Contributing = ( e1->OutIdx >= 0 ); bool e2Contributing = ( e2->OutIdx >= 0 ); -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ SetZ(Pt, *e1, *e2); #endif @@ -2641,7 +2645,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) (ePrev->Curr.x() == e->Curr.x()) && (ePrev->WindDelta != 0)) { IntPoint pt = e->Curr; -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ SetZ(pt, *ePrev, *e); #endif OutPt* op = AddOutPt(ePrev, pt); @@ -4204,3 +4208,7 @@ std::ostream& operator <<(std::ostream &s, const Paths &p) //------------------------------------------------------------------------------ } //ClipperLib namespace + +#ifdef CLIPPERLIB_NAMESPACE_PREFIX +} // namespace CLIPPERLIB_NAMESPACE_PREFIX +#endif // CLIPPERLIB_NAMESPACE_PREFIX diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 31919ce752c..c32bcf87b2c 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -41,8 +41,8 @@ #define CLIPPER_VERSION "6.2.6" -//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. -//#define use_xyz +//CLIPPERLIB_USE_XYZ: adds a Z member to IntPoint. Adds a minor cost to perfomance. +//#define CLIPPERLIB_USE_XYZ //use_lines: Enables line clipping. Adds a very minor cost to performance. #define use_lines @@ -59,11 +59,15 @@ #include #include -#ifdef use_xyz -namespace ClipperLib_Z { -#else /* use_xyz */ -namespace ClipperLib { -#endif /* use_xyz */ +#ifdef CLIPPERLIB_NAMESPACE_PREFIX + namespace CLIPPERLIB_NAMESPACE_PREFIX { +#endif // CLIPPERLIB_NAMESPACE_PREFIX + +#ifdef CLIPPERLIB_USE_XYZ + namespace ClipperLib_Z { +#else + namespace ClipperLib { +#endif enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; enum PolyType { ptSubject, ptClip }; @@ -90,43 +94,20 @@ enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; static constexpr cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; #endif // CLIPPERLIB_INT32 -#if 1 +#ifdef CLIPPERLIB_INTPOINT_TYPE +using IntPoint = CLIPPERLIB_INTPOINT_TYPE; +#else // CLIPPERLIB_INTPOINT_TYPE using IntPoint = Eigen::Matrix; -using DoublePoint = Eigen::Matrix; -#else -struct IntPoint { - cInt X; - cInt Y; -#ifdef use_xyz - cInt Z; - IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; -#else - IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; -#endif +#endif // CLIPPERLIB_INTPOINT_TYPE + +using DoublePoint = Eigen::Matrix; - friend inline bool operator== (const IntPoint& a, const IntPoint& b) - { - return a.X == b.X && a.Y == b.Y; - } - friend inline bool operator!= (const IntPoint& a, const IntPoint& b) - { - return a.X != b.X || a.Y != b.Y; - } -}; -struct DoublePoint -{ - double X; - double Y; - DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} - DoublePoint(IntPoint ip) : X((double)ip.x()), Y((double)ip.y()) {} -}; -#endif //------------------------------------------------------------------------------ typedef std::vector Path; @@ -141,7 +122,7 @@ std::ostream& operator <<(std::ostream &s, const Paths &p); //------------------------------------------------------------------------------ -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ typedef std::function ZFillCallback; #endif @@ -282,11 +263,11 @@ enum EdgeSide { esLeft = 1, esRight = 2}; }; // Point of an output polygon. - // 36B on 64bit system without use_xyz. + // 36B on 64bit system without CLIPPERLIB_USE_XYZ. struct OutPt { // 4B int Idx; - // 16B without use_xyz / 24B with use_xyz + // 16B without CLIPPERLIB_USE_XYZ / 24B with CLIPPERLIB_USE_XYZ IntPoint Pt; // 4B on 32bit system, 8B on 64bit system OutPt *Next; @@ -381,7 +362,7 @@ public: bool StrictlySimple() const {return m_StrictSimple;}; void StrictlySimple(bool value) {m_StrictSimple = value;}; //set the callback function for z value filling on intersections (otherwise Z is 0) -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ void ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; } #endif protected: @@ -414,7 +395,7 @@ private: // Does the result go to a PolyTree or Paths? bool m_UsingPolyTree; bool m_StrictSimple; -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ ZFillCallback m_ZFill; //custom callback #endif void SetWindingCount(TEdge& edge) const; @@ -467,7 +448,7 @@ private: void DoSimplePolygons(); void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const; void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const; -#ifdef use_xyz +#ifdef CLIPPERLIB_USE_XYZ void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); #endif }; @@ -519,6 +500,8 @@ class clipperException : public std::exception } //ClipperLib namespace +#ifdef CLIPPERLIB_NAMESPACE_PREFIX +} // namespace CLIPPERLIB_NAMESPACE_PREFIX +#endif // CLIPPERLIB_NAMESPACE_PREFIX + #endif //clipper_hpp - - diff --git a/src/clipper/clipper_z.cpp b/src/clipper/clipper_z.cpp index 4a54ef34679..f26be7089ad 100644 --- a/src/clipper/clipper_z.cpp +++ b/src/clipper/clipper_z.cpp @@ -1,7 +1,7 @@ // Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support. // Enable the Z coordinate support. -#define use_xyz +#define CLIPPERLIB_USE_XYZ // and let it compile #include "clipper.cpp" diff --git a/src/clipper/clipper_z.hpp b/src/clipper/clipper_z.hpp index e5e7d48ce18..20596d8e196 100644 --- a/src/clipper/clipper_z.hpp +++ b/src/clipper/clipper_z.hpp @@ -2,17 +2,17 @@ #ifndef clipper_z_hpp #ifdef clipper_hpp -#error "You should include the clipper_z.hpp first" +#error "You should include clipper_z.hpp before clipper.hpp" #endif #define clipper_z_hpp // Enable the Z coordinate support. -#define use_xyz +#define CLIPPERLIB_USE_XYZ #include "clipper.hpp" #undef clipper_hpp -#undef use_xyz +#undef CLIPPERLIB_USE_XYZ #endif // clipper_z_hpp diff --git a/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp b/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp index 49852318065..14b075b19da 100644 --- a/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp @@ -132,14 +132,14 @@ template<> inline void offset(Slic3r::ExPolygon& sh, coord_t distance, const PolygonTag&) { #define DISABLE_BOOST_OFFSET - auto res = Slic3r::offset_ex(sh, distance, ClipperLib::jtSquare); + auto res = Slic3r::offset_ex(sh, distance, Slic3r::ClipperLib::jtSquare); if (!res.empty()) sh = res.front(); } template<> inline void offset(Slic3r::Polygon& sh, coord_t distance, const PathTag&) { - auto res = Slic3r::offset(sh, distance, ClipperLib::jtSquare); + auto res = Slic3r::offset(sh, distance, Slic3r::ClipperLib::jtSquare); if (!res.empty()) sh = res.front(); } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index d458b03cf1b..61a32678b9d 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -54,7 +54,7 @@ namespace Slic3r { template, int...EigenArgs> inline constexpr Eigen::Matrix unscaled( - const ClipperLib::IntPoint &v) noexcept + const Slic3r::ClipperLib::IntPoint &v) noexcept { return Eigen::Matrix{unscaled(v.x()), unscaled(v.y())}; @@ -616,7 +616,7 @@ void arrange(ArrangePolygons & arrangables, const BedT & bed, const ArrangeParams & params) { - namespace clppr = ClipperLib; + namespace clppr = Slic3r::ClipperLib; std::vector items, fixeditems; items.reserve(arrangables.size()); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2abe94656d8..16299f442dc 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -23,6 +23,8 @@ add_library(libslic3r STATIC BridgeDetector.hpp Brim.cpp Brim.hpp + clipper.cpp + clipper.hpp ClipperUtils.cpp ClipperUtils.hpp Config.cpp diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 6668a9ae9bd..0a34fc93bbb 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -8,9 +8,9 @@ #include "Surface.hpp" // import these wherever we're included -using ClipperLib::jtMiter; -using ClipperLib::jtRound; -using ClipperLib::jtSquare; +using Slic3r::ClipperLib::jtMiter; +using Slic3r::ClipperLib::jtRound; +using Slic3r::ClipperLib::jtSquare; #define CLIPPERUTILS_UNSAFE_OFFSET diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index f6f4f56181d..c6af515c830 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -22,12 +22,14 @@ #pragma warning(pop) #endif // _MSC_VER -namespace ClipperLib { -class PolyNode; -using PolyNodes = std::vector; -} +namespace Slic3r { -namespace Slic3r { namespace Geometry { + namespace ClipperLib { + class PolyNode; + using PolyNodes = std::vector; + } + +namespace Geometry { // Generic result of an orientation predicate. enum Orientation @@ -530,6 +532,6 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation) return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z()); } -} } +} } // namespace Slicer::Geometry #endif diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index 9386fdf369d..b55755b8922 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -114,7 +114,7 @@ #include #include -#include +#include "clipper.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Config.hpp" @@ -129,8 +129,6 @@ #include "libslic3r.h" #include "libslic3r_version.h" -#include "clipper.hpp" - #include #include diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index 181f130e556..97c7ef99ddc 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -1152,7 +1152,7 @@ template MultiPolygon merged_pile(It from, It to, int bin_id) TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]") { - static const constexpr ClipperLib::cInt W = 10000000; + static const constexpr Slic3r::ClipperLib::cInt W = 10000000; // Get the input items and define the bin. std::vector input(9, {W, W}); @@ -1187,7 +1187,7 @@ TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels] TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") { - static const constexpr ClipperLib::cInt W = 10000000; + static const constexpr Slic3r::ClipperLib::cInt W = 10000000; static const constexpr size_t N = 100; // Get the input items and define the bin. diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index 277b5982599..a39db61400d 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -2,7 +2,6 @@ %{ #include -#include "clipper.hpp" #include "libslic3r/ClipperUtils.hpp" %} From 2d3d42dbff0ec45aa22ff82a7b910e490adab493 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 22 Apr 2021 09:26:07 +0200 Subject: [PATCH 125/154] Added missing files --- src/libslic3r/clipper.cpp | 13 +++++++++++++ src/libslic3r/clipper.hpp | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/libslic3r/clipper.cpp create mode 100644 src/libslic3r/clipper.hpp diff --git a/src/libslic3r/clipper.cpp b/src/libslic3r/clipper.cpp new file mode 100644 index 00000000000..8f38ba6215e --- /dev/null +++ b/src/libslic3r/clipper.cpp @@ -0,0 +1,13 @@ +// Hackish wrapper around the ClipperLib library to compile the Clipper library using Slic3r::Point. + +#include "clipper.hpp" + +// Don't include for the second time. +#define clipper_hpp + +// Override ClipperLib namespace to Slic3r::ClipperLib +#define CLIPPERLIB_NAMESPACE_PREFIX Slic3r +// Override Slic3r::ClipperLib::IntPoint to Slic3r::Point +#define CLIPPERLIB_INTPOINT_TYPE Slic3r::Point + +#include diff --git a/src/libslic3r/clipper.hpp b/src/libslic3r/clipper.hpp new file mode 100644 index 00000000000..b0dd51a4f7f --- /dev/null +++ b/src/libslic3r/clipper.hpp @@ -0,0 +1,26 @@ +// Hackish wrapper around the ClipperLib library to compile the Clipper library using Slic3r's own Point type. + +#ifndef slic3r_clipper_hpp + +#ifdef clipper_hpp +#error "You should include the libslic3r/clipper.hpp before clipper/clipper.hpp" +#endif + +#ifdef CLIPPERLIB_USE_XYZ +#error "Something went wrong. Using clipper.hpp with Slic3r Point type, but CLIPPERLIB_USE_XYZ is defined." +#endif + +#define slic3r_clipper_hpp + +#include "Point.hpp" + +#define CLIPPERLIB_NAMESPACE_PREFIX Slic3r +#define CLIPPERLIB_INTPOINT_TYPE Slic3r::Point + +#include + +#undef clipper_hpp +#undef CLIPPERLIB_NAMESPACE_PREFIX +#undef CLIPPERLIB_INTPOINT_TYPE + +#endif // slic3r_clipper_hpp From 0fa17516d773e232e86e2365f23b8ec943792412 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 22 Apr 2021 09:44:08 +0200 Subject: [PATCH 126/154] Move iterator stuff from polygon to multipoint --- src/libslic3r/MultiPoint.hpp | 7 +++++++ src/libslic3r/Polygon.hpp | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index d17142bb2ca..46b47832a7e 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -84,6 +84,13 @@ public: static Points _douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); + + inline auto begin() { return points.begin(); } + inline auto begin() const { return points.begin(); } + inline auto end() { return points.end(); } + inline auto end() const { return points.end(); } + inline auto cbegin() const { return points.begin(); } + inline auto cend() const { return points.end(); } }; class MultiPoint3 diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 01d4d3decaf..93cd7012134 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -75,13 +75,6 @@ public: using iterator = Points::iterator; using const_iterator = Points::const_iterator; - - inline auto begin() { return points.begin(); } - inline auto begin() const { return points.begin(); } - inline auto end() { return points.end(); } - inline auto end() const { return points.end(); } - inline auto cbegin() const { return points.begin(); } - inline auto cend() const { return points.end(); } }; inline bool operator==(const Polygon &lhs, const Polygon &rhs) { return lhs.points == rhs.points; } From f9ddb38f041e96e8dbfe79d2498578d3874de880 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 22 Apr 2021 15:15:19 +0200 Subject: [PATCH 127/154] Extrusions in custom start g-code forced to be at first layer height level --- src/libslic3r/GCode/GCodeProcessor.cpp | 33 +++++++++++++++++++++++--- src/libslic3r/GCode/GCodeProcessor.hpp | 4 ++++ src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/GCodeViewer.cpp | 4 ++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index d5be041f4c2..5813364ea20 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -857,6 +857,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_time_processor.export_remaining_time_enabled = config.remaining_times.value; m_use_volumetric_e = config.use_volumetric_e; + +#if ENABLE_START_GCODE_VISUALIZATION + const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); + if (first_layer_height != nullptr) + m_first_layer_height = std::abs(first_layer_height->value); +#endif // ENABLE_START_GCODE_VISUALIZATION } void GCodeProcessor::apply_config(const DynamicPrintConfig& config) @@ -1035,6 +1041,12 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionBool* use_volumetric_e = config.option("use_volumetric_e"); if (use_volumetric_e != nullptr) m_use_volumetric_e = use_volumetric_e->value; + +#if ENABLE_START_GCODE_VISUALIZATION + const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); + if (first_layer_height != nullptr) + m_first_layer_height = std::abs(first_layer_height->value); +#endif // ENABLE_START_GCODE_VISUALIZATION } void GCodeProcessor::enable_stealth_time_estimator(bool enabled) @@ -1082,6 +1094,10 @@ void GCodeProcessor::reset() m_filament_diameters = std::vector(Min_Extruder_Count, 1.75f); m_extruded_last_z = 0.0f; +#if ENABLE_START_GCODE_VISUALIZATION + m_first_layer_height = 0.0f; + m_processing_start_custom_gcode = false; +#endif // ENABLE_START_GCODE_VISUALIZATION m_g1_line_id = 0; m_layer_id = 0; m_cp_color.reset(); @@ -1443,6 +1459,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) // extrusion role tag if (boost::starts_with(comment, reserved_tag(ETags::Role))) { m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length())); +#if ENABLE_START_GCODE_VISUALIZATION + m_processing_start_custom_gcode = (m_extrusion_role == erCustom && m_g1_line_id == 0); +#endif // ENABLE_START_GCODE_VISUALIZATION return; } @@ -2187,7 +2206,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } +#if ENABLE_START_GCODE_VISUALIZATION if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) +#else + if (type == EMoveType::Extrude && (m_extrusion_role == erCustom || m_width == 0.0f || m_height == 0.0f)) +#endif // ENABLE_START_GCODE_VISUALIZATION type = EMoveType::Travel; // time estimate section @@ -2303,13 +2326,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. float jerk = (v_exit > v_entry) ? - (((v_entry > 0.0f) || (v_exit < 0.0f)) ? + ((v_entry > 0.0f || v_exit < 0.0f) ? // coasting (v_exit - v_entry) : // axis reversal std::max(v_exit, -v_entry)) : // v_exit <= v_entry - (((v_entry < 0.0f) || (v_exit > 0.0f)) ? + ((v_entry < 0.0f || v_exit > 0.0f) ? // coasting (v_entry - v_exit) : // axis reversal @@ -2330,7 +2353,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) float vmax_junction_threshold = vmax_junction * 0.99f; // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start. - if ((prev.safe_feedrate > vmax_junction_threshold) && (curr.safe_feedrate > vmax_junction_threshold)) + if (prev.safe_feedrate > vmax_junction_threshold && curr.safe_feedrate > vmax_junction_threshold) vmax_junction = curr.safe_feedrate; } @@ -2815,7 +2838,11 @@ void GCodeProcessor::store_move_vertex(EMoveType type) m_extrusion_role, m_extruder_id, m_cp_color.current, +#if ENABLE_START_GCODE_VISUALIZATION + Vec3f(m_end_position[X], m_end_position[Y], m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]) + m_extruder_offsets[m_extruder_id], +#else Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z]) + m_extruder_offsets[m_extruder_id], +#endif // ENABLE_START_GCODE_VISUALIZATION m_end_position[E] - m_start_position[E], m_feedrate, m_width, diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index cf55bf86e79..3dc30cf6905 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -490,6 +490,10 @@ namespace Slic3r { ExtruderTemps m_extruder_temps; std::vector m_filament_diameters; float m_extruded_last_z; +#if ENABLE_START_GCODE_VISUALIZATION + float m_first_layer_height; // mm + bool m_processing_start_custom_gcode; +#endif // ENABLE_START_GCODE_VISUALIZATION unsigned int m_g1_line_id; unsigned int m_layer_id; CpColor m_cp_color; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 303ffe92741..cba9cee14ec 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,8 @@ #define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) // Enable a modified version of automatic downscale on load of objects too big #define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) +// Enable visualization of start gcode as regular toolpaths +#define ENABLE_START_GCODE_VISUALIZATION (1 && ENABLE_2_4_0_ALPHA0) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 67a489c7b6b..32aba57244b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1714,7 +1714,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // for the gcode viewer we need to take in account all moves to correctly size the printbed m_paths_bounding_box.merge(move.position.cast()); else { +#if ENABLE_START_GCODE_VISUALIZATION if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) +#else + if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) +#endif // ENABLE_START_GCODE_VISUALIZATION m_paths_bounding_box.merge(move.position.cast()); } } From 065bf79257521cbf08e248cc1a545e537b957184 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 23 Apr 2021 11:02:16 +0200 Subject: [PATCH 128/154] Fixed Perl bindings of Clipper after Clipper was adapted to Slic3r::Point --- xs/xsp/Clipper.xsp | 16 ++++++++-------- xs/xsp/Polyline.xsp | 4 ++-- xs/xsp/Surface.xsp | 2 +- xs/xsp/my.map | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index a39db61400d..c4640dcf4f9 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -20,10 +20,10 @@ _constant() OUTPUT: RETVAL Polygons -offset(polygons, delta, joinType = ClipperLib::jtMiter, miterLimit = 3) +offset(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta - ClipperLib::JoinType joinType + Slic3r::ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset(polygons, delta, joinType, miterLimit); @@ -31,10 +31,10 @@ offset(polygons, delta, joinType = ClipperLib::jtMiter, miterLimit = 3) RETVAL ExPolygons -offset_ex(polygons, delta, joinType = ClipperLib::jtMiter, miterLimit = 3) +offset_ex(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta - ClipperLib::JoinType joinType + Slic3r::ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset_ex(polygons, delta, joinType, miterLimit); @@ -42,11 +42,11 @@ offset_ex(polygons, delta, joinType = ClipperLib::jtMiter, miterLimit = 3) RETVAL Polygons -offset2(polygons, delta1, delta2, joinType = ClipperLib::jtMiter, miterLimit = 3) +offset2(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta1 const float delta2 - ClipperLib::JoinType joinType + Slic3r::ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset2(polygons, delta1, delta2, joinType, miterLimit); @@ -54,11 +54,11 @@ offset2(polygons, delta1, delta2, joinType = ClipperLib::jtMiter, miterLimit = 3 RETVAL ExPolygons -offset2_ex(polygons, delta1, delta2, joinType = ClipperLib::jtMiter, miterLimit = 3) +offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta1 const float delta2 - ClipperLib::JoinType joinType + Slic3r::ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit); diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 0dbd0e5728f..10bbb263f8b 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -79,9 +79,9 @@ Polyline::rotate(angle, center_sv) THIS->rotate(angle, center); Polygons -Polyline::grow(delta, joinType = ClipperLib::jtSquare, miterLimit = 3) +Polyline::grow(delta, joinType = Slic3r::ClipperLib::jtSquare, miterLimit = 3) const float delta - ClipperLib::JoinType joinType + Slic3r::ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset(*THIS, delta, joinType, miterLimit); diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp index 379774f0a44..49d988333c6 100644 --- a/xs/xsp/Surface.xsp +++ b/xs/xsp/Surface.xsp @@ -85,7 +85,7 @@ Surface::polygons() Surfaces Surface::offset(delta, joinType = ClipperLib::jtMiter, miterLimit = 3) const float delta - ClipperLib::JoinType joinType + Slic3r::ClipperLib::JoinType joinType double miterLimit CODE: surfaces_append(RETVAL, offset_ex(THIS->expolygon, delta, joinType, miterLimit), *THIS); diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 2ecff6e3f4b..54e686ae36e 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -211,8 +211,8 @@ FlowRole T_UV PrintStep T_UV PrintObjectStep T_UV SurfaceType T_UV -ClipperLib::JoinType T_UV -ClipperLib::PolyFillType T_UV +Slic3r::ClipperLib::JoinType T_UV +Slic3r::ClipperLib::PolyFillType T_UV # we return these types whenever we want the items to be cloned Points T_ARRAYREF From fbdf0d6ab06f650c53a7f7c006b7e2b5f15b28e0 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 26 Apr 2021 09:21:16 +0200 Subject: [PATCH 129/154] Wipe tower priming lines are placed at origin with custom bed shapes Custom shapes were previously detected as circular and the lines were placed off the bed --- src/libslic3r/GCode/WipeTower.cpp | 20 +++++++++++++++++--- src/libslic3r/GCode/WipeTower.hpp | 3 ++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 10c68d07665..86a6616ee2d 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -546,10 +546,24 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector& bed_points = config.bed_shape.values; + BoundingBoxf bb(bed_points); + m_bed_width = float(bb.size().x()); m_bed_shape = (bed_points.size() == 4 ? RectangularBed : CircularBed); - m_bed_width = float(BoundingBoxf(bed_points).size().x()); + + if (m_bed_shape == CircularBed) { + // this may still be a custom bed, check that the points are roughly on a circle + double r2 = std::pow(m_bed_width/2., 2.); + double lim2 = std::pow(m_bed_width/10., 2.); + Vec2d center = bb.center(); + for (const Vec2d& pt : bed_points) + if (std::abs(std::pow(pt.x()-center.x(), 2.) + std::pow(pt.y()-center.y(), 2.) - r2) > lim2) { + m_bed_shape = CustomBed; + break; + } + } + m_bed_bottom_left = m_bed_shape == RectangularBed ? Vec2f(bed_points.front().x(), bed_points.front().y()) : Vec2f::Zero(); @@ -628,7 +642,7 @@ std::vector WipeTower::prime( // In case of a circular bed, place it so it goes across the diameter and hope it will fit if (m_bed_shape == CircularBed) cleaning_box.translate(-m_bed_width/2 + m_bed_width * 0.03f, -m_bed_width * 0.12f); - if (m_bed_shape == RectangularBed) + else cleaning_box.translate(m_bed_bottom_left); std::vector results; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index c3b2770b709..6fd7a8e211c 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -277,7 +277,8 @@ private: // Bed properties enum { RectangularBed, - CircularBed + CircularBed, + CustomBed } m_bed_shape; float m_bed_width; // width of the bed bounding box Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds) From 9c606e7948d332910708667a667d4ec984c55a1c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 26 Apr 2021 18:37:10 +0200 Subject: [PATCH 130/154] Refactoring of StaticPrintConfig & derived classes: 1) Using boost::preprocessor to reduce code duplicities when defining new configuration values. 2) Implemented static hash() and operator== on StaticPrintConfig derived classes to support hash tables of instances thereof. --- src/libslic3r/Config.hpp | 124 ++- src/libslic3r/GCode/CoolingBuffer.cpp | 2 +- src/libslic3r/GCodeReader.cpp | 4 +- src/libslic3r/GCodeWriter.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 88 +- src/libslic3r/PrintConfig.hpp | 1285 ++++++++++--------------- xs/xsp/Config.xsp | 2 +- 7 files changed, 619 insertions(+), 888 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 9aab435edd2..c49c492a3f8 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -18,11 +18,53 @@ #include #include +#include #include #include #include +namespace Slic3r { + struct FloatOrPercent + { + double value; + bool percent; + + private: + friend class cereal::access; + template void serialize(Archive& ar) { ar(this->value); ar(this->percent); } + }; + + inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value == r.value && l.percent == r.percent; } + inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return !(l == r); } +} + +namespace std { + template<> struct hash { + std::size_t operator()(const Slic3r::FloatOrPercent& v) const noexcept { + std::size_t seed = std::hash{}(v.value); + return v.percent ? seed ^ 0x9e3779b9 : seed; + } + }; + + template<> struct hash { + std::size_t operator()(const Slic3r::Vec2d& v) const noexcept { + std::size_t seed = std::hash{}(v.x()); + boost::hash_combine(seed, std::hash{}(v.y())); + return seed; + } + }; + + template<> struct hash { + std::size_t operator()(const Slic3r::Vec3d& v) const noexcept { + std::size_t seed = std::hash{}(v.x()); + boost::hash_combine(seed, std::hash{}(v.y())); + boost::hash_combine(seed, std::hash{}(v.z())); + return seed; + } + }; +} + namespace Slic3r { // Name of the configuration option. @@ -137,6 +179,7 @@ public: virtual void setInt(int /* val */) { throw BadOptionTypeException("Calling ConfigOption::setInt on a non-int ConfigOption"); } virtual bool operator==(const ConfigOption &rhs) const = 0; bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); } + virtual size_t hash() const throw() = 0; bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; } bool is_vector() const { return ! this->is_scalar(); } // If this option is nullable, then it may have its value or values set to nil. @@ -185,8 +228,10 @@ public: return this->value == static_cast*>(&rhs)->value; } - bool operator==(const T &rhs) const { return this->value == rhs; } - bool operator!=(const T &rhs) const { return this->value != rhs; } + bool operator==(const T &rhs) const throw() { return this->value == rhs; } + bool operator!=(const T &rhs) const throw() { return this->value != rhs; } + + size_t hash() const throw() override { return std::hash{}(this->value); } private: friend class cereal::access; @@ -339,8 +384,16 @@ public: return this->values == static_cast*>(&rhs)->values; } - bool operator==(const std::vector &rhs) const { return this->values == rhs; } - bool operator!=(const std::vector &rhs) const { return this->values != rhs; } + bool operator==(const std::vector &rhs) const throw() { return this->values == rhs; } + bool operator!=(const std::vector &rhs) const throw() { return this->values != rhs; } + + size_t hash() const throw() override { + std::hash hasher; + size_t seed = 0; + for (const auto &v : this->values) + boost::hash_combine(seed, hasher(v)); + return seed; + } // Is this option overridden by another option? // An option overrides another option if it is not nil and not equal. @@ -413,7 +466,7 @@ public: ConfigOptionType type() const override { return static_type(); } double getFloat() const override { return this->value; } ConfigOption* clone() const override { return new ConfigOptionFloat(*this); } - bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionFloat &rhs) const throw() { return this->value == rhs.value; } std::string serialize() const override { @@ -454,7 +507,7 @@ public: static ConfigOptionType static_type() { return coFloats; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionFloatsTempl(*this); } - bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } + bool operator==(const ConfigOptionFloatsTempl &rhs) const throw() { return vectors_equal(this->values, rhs.values); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types"); @@ -566,7 +619,7 @@ public: int getInt() const override { return this->value; } void setInt(int val) override { this->value = val; } ConfigOption* clone() const override { return new ConfigOptionInt(*this); } - bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionInt &rhs) const throw() { return this->value == rhs.value; } std::string serialize() const override { @@ -606,7 +659,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionIntsTempl(*this); } ConfigOptionIntsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionIntsTempl &rhs) const { return this->values == rhs.values; } + bool operator==(const ConfigOptionIntsTempl &rhs) const throw() { return this->values == rhs.values; } // Could a special "nil" value be stored inside the vector, indicating undefined value? bool nullable() const override { return NULLABLE; } // Special "nil" value to be stored into the vector if this->supports_nil(). @@ -689,7 +742,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionString(*this); } ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionString &rhs) const throw() { return this->value == rhs.value; } bool empty() const { return this->value.empty(); } std::string serialize() const override @@ -722,7 +775,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; } + bool operator==(const ConfigOptionStrings &rhs) const throw() { return this->values == rhs.values; } bool is_nil(size_t) const override { return false; } std::string serialize() const override @@ -757,7 +810,8 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPercent(*this); } ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionPercent &rhs) const throw() { return this->value == rhs.value; } + double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; } std::string serialize() const override @@ -796,8 +850,8 @@ public: static ConfigOptionType static_type() { return coPercents; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPercentsTempl(*this); } - ConfigOptionPercentsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPercentsTempl &rhs) const { return this->values == rhs.values; } + ConfigOptionPercentsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPercentsTempl &rhs) const throw() { return this->values == rhs.values; } std::string serialize() const override { @@ -856,8 +910,12 @@ public: assert(dynamic_cast(&rhs)); return *this == *static_cast(&rhs); } - bool operator==(const ConfigOptionFloatOrPercent &rhs) const + bool operator==(const ConfigOptionFloatOrPercent &rhs) const throw() { return this->value == rhs.value && this->percent == rhs.percent; } + size_t hash() const throw() override { + size_t seed = std::hash{}(this->value); + return this->percent ? seed ^ 0x9e3779b9 : seed; + } double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; } @@ -891,27 +949,6 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class(this), percent); } }; - -struct FloatOrPercent -{ - double value; - bool percent; - -private: - friend class cereal::access; - template void serialize(Archive & ar) { ar(this->value); ar(this->percent); } -}; - -inline bool operator==(const FloatOrPercent &l, const FloatOrPercent &r) -{ - return l.value == r.value && l.percent == r.percent; -} - -inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) -{ - return !(l == r); -} - template class ConfigOptionFloatsOrPercentsTempl : public ConfigOptionVector { @@ -925,13 +962,14 @@ public: static ConfigOptionType static_type() { return coFloatsOrPercents; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionFloatsOrPercentsTempl(*this); } - bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } + bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const throw() { return vectors_equal(this->values, rhs.values); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return vectors_equal(this->values, static_cast*>(&rhs)->values); } + // Could a special "nil" value be stored inside the vector, indicating undefined value? bool nullable() const override { return NULLABLE; } // Special "nil" value to be stored into the vector if this->supports_nil(). @@ -1038,7 +1076,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPoint(*this); } ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionPoint &rhs) const throw() { return this->value == rhs.value; } std::string serialize() const override { @@ -1074,7 +1112,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; } + bool operator==(const ConfigOptionPoints &rhs) const throw() { return this->values == rhs.values; } bool is_nil(size_t) const override { return false; } std::string serialize() const override @@ -1146,7 +1184,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPoint3(*this); } ConfigOptionPoint3& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPoint3 &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionPoint3 &rhs) const throw() { return this->value == rhs.value; } std::string serialize() const override { @@ -1183,7 +1221,7 @@ public: bool getBool() const override { return this->value; } ConfigOption* clone() const override { return new ConfigOptionBool(*this); } ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionBool &rhs) const throw() { return this->value == rhs.value; } std::string serialize() const override { @@ -1217,7 +1255,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionBoolsTempl(*this); } ConfigOptionBoolsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionBoolsTempl &rhs) const { return this->values == rhs.values; } + bool operator==(const ConfigOptionBoolsTempl &rhs) const throw() { return this->values == rhs.values; } // Could a special "nil" value be stored inside the vector, indicating undefined value? bool nullable() const override { return NULLABLE; } // Special "nil" value to be stored into the vector if this->supports_nil(). @@ -1311,7 +1349,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionEnum(*this); } ConfigOptionEnum& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionEnum &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionEnum &rhs) const throw() { return this->value == rhs.value; } int getInt() const override { return (int)this->value; } void setInt(int val) override { this->value = T(val); } @@ -1397,7 +1435,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); } ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOptionEnumGeneric &rhs) const throw() { return this->value == rhs.value; } bool operator==(const ConfigOption &rhs) const override { diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 7f48aae808b..07b9b07bbc6 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -326,7 +326,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; const char *line_start = gcode.c_str(); const char *line_end = line_start; - const char extrusion_axis = config.get_extrusion_axis()[0]; + const char extrusion_axis = get_extrusion_axis(config)[0]; // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index fb493fcb7e3..9753e482063 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -13,13 +13,13 @@ namespace Slic3r { void GCodeReader::apply_config(const GCodeConfig &config) { m_config = config; - m_extrusion_axis = m_config.get_extrusion_axis()[0]; + m_extrusion_axis = get_extrusion_axis(m_config)[0]; } void GCodeReader::apply_config(const DynamicPrintConfig &config) { m_config.apply(config, true); - m_extrusion_axis = m_config.get_extrusion_axis()[0]; + m_extrusion_axis = get_extrusion_axis(m_config)[0]; } const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair &command) diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index bc853311379..c84c7b09ed1 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -18,7 +18,7 @@ namespace Slic3r { void GCodeWriter::apply_print_config(const PrintConfig &print_config) { this->config.apply(print_config, true); - m_extrusion_axis = this->config.get_extrusion_axis(); + m_extrusion_axis = get_extrusion_axis(this->config); m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; bool is_marlin = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware; m_max_acceleration = std::lrint((is_marlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 9f09bc9f34d..f2c5f70a971 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3608,7 +3608,7 @@ std::string DynamicPrintConfig::validate() FullPrintConfig fpc; fpc.apply(*this, true); // Verify this print options through the FullPrintConfig. - return fpc.validate(); + return Slic3r::validate(fpc); } default: //FIXME no validation on SLA data? @@ -3617,135 +3617,135 @@ std::string DynamicPrintConfig::validate() } //FIXME localize this function. -std::string FullPrintConfig::validate() +std::string validate(const FullPrintConfig &cfg) { // --layer-height - if (this->get_abs_value("layer_height") <= 0) + if (cfg.get_abs_value("layer_height") <= 0) return "Invalid value for --layer-height"; - if (fabs(fmod(this->get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4) + if (fabs(fmod(cfg.get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4) return "--layer-height must be a multiple of print resolution"; // --first-layer-height - if (first_layer_height.value <= 0) + if (cfg.first_layer_height.value <= 0) return "Invalid value for --first-layer-height"; // --filament-diameter - for (double fd : this->filament_diameter.values) + for (double fd : cfg.filament_diameter.values) if (fd < 1) return "Invalid value for --filament-diameter"; // --nozzle-diameter - for (double nd : this->nozzle_diameter.values) + for (double nd : cfg.nozzle_diameter.values) if (nd < 0.005) return "Invalid value for --nozzle-diameter"; // --perimeters - if (this->perimeters.value < 0) + if (cfg.perimeters.value < 0) return "Invalid value for --perimeters"; // --solid-layers - if (this->top_solid_layers < 0) + if (cfg.top_solid_layers < 0) return "Invalid value for --top-solid-layers"; - if (this->bottom_solid_layers < 0) + if (cfg.bottom_solid_layers < 0) return "Invalid value for --bottom-solid-layers"; - if (this->use_firmware_retraction.value && - this->gcode_flavor.value != gcfSmoothie && - this->gcode_flavor.value != gcfRepRapSprinter && - this->gcode_flavor.value != gcfRepRapFirmware && - this->gcode_flavor.value != gcfMarlinLegacy && - this->gcode_flavor.value != gcfMarlinFirmware && - this->gcode_flavor.value != gcfMachinekit && - this->gcode_flavor.value != gcfRepetier) + if (cfg.use_firmware_retraction.value && + cfg.gcode_flavor.value != gcfSmoothie && + cfg.gcode_flavor.value != gcfRepRapSprinter && + cfg.gcode_flavor.value != gcfRepRapFirmware && + cfg.gcode_flavor.value != gcfMarlinLegacy && + cfg.gcode_flavor.value != gcfMarlinFirmware && + cfg.gcode_flavor.value != gcfMachinekit && + cfg.gcode_flavor.value != gcfRepetier) return "--use-firmware-retraction is only supported by Marlin, Smoothie, RepRapFirmware, Repetier and Machinekit firmware"; - if (this->use_firmware_retraction.value) - for (unsigned char wipe : this->wipe.values) + if (cfg.use_firmware_retraction.value) + for (unsigned char wipe : cfg.wipe.values) if (wipe) return "--use-firmware-retraction is not compatible with --wipe"; // --gcode-flavor - if (! print_config_def.get("gcode_flavor")->has_enum_value(this->gcode_flavor.serialize())) + if (! print_config_def.get("gcode_flavor")->has_enum_value(cfg.gcode_flavor.serialize())) return "Invalid value for --gcode-flavor"; // --fill-pattern - if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize())) + if (! print_config_def.get("fill_pattern")->has_enum_value(cfg.fill_pattern.serialize())) return "Invalid value for --fill-pattern"; // --top-fill-pattern - if (! print_config_def.get("top_fill_pattern")->has_enum_value(this->top_fill_pattern.serialize())) + if (! print_config_def.get("top_fill_pattern")->has_enum_value(cfg.top_fill_pattern.serialize())) return "Invalid value for --top-fill-pattern"; // --bottom-fill-pattern - if (! print_config_def.get("bottom_fill_pattern")->has_enum_value(this->bottom_fill_pattern.serialize())) + if (! print_config_def.get("bottom_fill_pattern")->has_enum_value(cfg.bottom_fill_pattern.serialize())) return "Invalid value for --bottom-fill-pattern"; // --fill-density - if (fabs(this->fill_density.value - 100.) < EPSILON && - ! print_config_def.get("top_fill_pattern")->has_enum_value(this->fill_pattern.serialize())) + if (fabs(cfg.fill_density.value - 100.) < EPSILON && + ! print_config_def.get("top_fill_pattern")->has_enum_value(cfg.fill_pattern.serialize())) return "The selected fill pattern is not supposed to work at 100% density"; // --infill-every-layers - if (this->infill_every_layers < 1) + if (cfg.infill_every_layers < 1) return "Invalid value for --infill-every-layers"; // --skirt-height - if (this->skirt_height < 0) + if (cfg.skirt_height < 0) return "Invalid value for --skirt-height"; // --bridge-flow-ratio - if (this->bridge_flow_ratio <= 0) + if (cfg.bridge_flow_ratio <= 0) return "Invalid value for --bridge-flow-ratio"; // extruder clearance - if (this->extruder_clearance_radius <= 0) + if (cfg.extruder_clearance_radius <= 0) return "Invalid value for --extruder-clearance-radius"; - if (this->extruder_clearance_height <= 0) + if (cfg.extruder_clearance_height <= 0) return "Invalid value for --extruder-clearance-height"; // --extrusion-multiplier - for (double em : this->extrusion_multiplier.values) + for (double em : cfg.extrusion_multiplier.values) if (em <= 0) return "Invalid value for --extrusion-multiplier"; // --default-acceleration - if ((this->perimeter_acceleration != 0. || this->infill_acceleration != 0. || this->bridge_acceleration != 0. || this->first_layer_acceleration != 0.) && - this->default_acceleration == 0.) + if ((cfg.perimeter_acceleration != 0. || cfg.infill_acceleration != 0. || cfg.bridge_acceleration != 0. || cfg.first_layer_acceleration != 0.) && + cfg.default_acceleration == 0.) return "Invalid zero value for --default-acceleration when using other acceleration settings"; // --spiral-vase - if (this->spiral_vase) { + if (cfg.spiral_vase) { // Note that we might want to have more than one perimeter on the bottom // solid layers. - if (this->perimeters > 1) + if (cfg.perimeters > 1) return "Can't make more than one perimeter when spiral vase mode is enabled"; - else if (this->perimeters < 1) + else if (cfg.perimeters < 1) return "Can't make less than one perimeter when spiral vase mode is enabled"; - if (this->fill_density > 0) + if (cfg.fill_density > 0) return "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0"; - if (this->top_solid_layers > 0) + if (cfg.top_solid_layers > 0) return "Spiral vase mode is not compatible with top solid layers"; - if (this->support_material || this->support_material_enforce_layers > 0) + if (cfg.support_material || cfg.support_material_enforce_layers > 0) return "Spiral vase mode is not compatible with support material"; } // extrusion widths { double max_nozzle_diameter = 0.; - for (double dmr : this->nozzle_diameter.values) + for (double dmr : cfg.nozzle_diameter.values) max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); const char *widths[] = { "external_perimeter", "perimeter", "infill", "solid_infill", "top_infill", "support_material", "first_layer" }; for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++ i) { std::string key(widths[i]); key += "_extrusion_width"; - if (this->get_abs_value(key, max_nozzle_diameter) > 10. * max_nozzle_diameter) + if (cfg.get_abs_value(key, max_nozzle_diameter) > 10. * max_nozzle_diameter) return std::string("Invalid extrusion width (too large): ") + key; } } // Out of range validation of numeric values. - for (const std::string &opt_key : this->keys()) { - const ConfigOption *opt = this->optptr(opt_key); + for (const std::string &opt_key : cfg.keys()) { + const ConfigOption *opt = cfg.optptr(opt_key); assert(opt != nullptr); const ConfigOptionDef *optdef = print_config_def.get(opt_key); assert(optdef != nullptr); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 74cb5c7748f..7209bea899a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -19,6 +19,14 @@ #include "libslic3r.h" #include "Config.hpp" +#include +#include +#include +#include +#include +#include +#include + // #define HAS_PRESSURE_EQUALIZER namespace Slic3r { @@ -29,10 +37,10 @@ enum GCodeFlavor : unsigned char { }; enum class MachineLimitsUsage { - EmitToGCode, - TimeEstimateOnly, - Ignore, - Count, + EmitToGCode, + TimeEstimateOnly, + Ignore, + Count, }; enum PrintHostType { @@ -55,10 +63,10 @@ enum InfillPattern : int { }; enum class IroningType { - TopSurfaces, - TopmostOnly, - AllSolid, - Count, + TopSurfaces, + TopmostOnly, + AllSolid, + Count, }; enum SupportMaterialPattern { @@ -136,7 +144,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum m_extruder_option_keys; - std::vector m_extruder_retract_keys; + std::vector m_extruder_option_keys; + std::vector m_extruder_retract_keys; }; // The one and only global definition of SLic3r configuration options. @@ -337,7 +345,7 @@ public: void normalize_fdm(); - void set_num_extruders(unsigned int num_extruders); + void set_num_extruders(unsigned int num_extruders); // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); @@ -358,7 +366,7 @@ public: // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &print_config_def; } // Reference to the cached list of keys. - virtual const t_config_option_keys& keys_ref() const = 0; + virtual const t_config_option_keys& keys_ref() const = 0; protected: // Verify whether the opt_key has not been obsoleted or renamed. @@ -482,729 +490,498 @@ public: \ void handle_legacy(t_config_option_key &opt_key, std::string &value) const override \ { PrintConfigDef::handle_legacy(opt_key, value); } -#define OPT_PTR(KEY) cache.opt_add(#KEY, base_ptr, this->KEY) +#define PRINT_CONFIG_CLASS_ELEMENT_DEFINITION(r, data, elem) BOOST_PP_TUPLE_ELEM(0, elem) BOOST_PP_TUPLE_ELEM(1, elem); +#define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(KEY) cache.opt_add(BOOST_PP_STRINGIZE(KEY), base_ptr, this->KEY); +#define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION(r, data, elem) PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(BOOST_PP_TUPLE_ELEM(1, elem)) +#define PRINT_CONFIG_CLASS_ELEMENT_HASH(r, data, elem) boost::hash_combine(seed, BOOST_PP_TUPLE_ELEM(1, elem).hash()); +#define PRINT_CONFIG_CLASS_ELEMENT_EQUAL(r, data, elem) if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false; + +#define PRINT_CONFIG_CLASS_DEFINE(CLASS_NAME, PARAMETER_DEFINITION_SEQ) \ +class CLASS_NAME : public StaticPrintConfig { \ + STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ +public: \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ) \ + size_t hash() const throw() \ + { \ + size_t seed = 0; \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ) \ + return seed; \ + } \ + bool operator==(const CLASS_NAME &rhs) const throw() \ + { \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ) \ + return true; \ + } \ + bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \ +protected: \ + void initialize(StaticCacheBase &cache, const char *base_ptr) \ + { \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ) \ + } \ +}; + +#define PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM(r, data, i, elem) BOOST_PP_COMMA_IF(i) public elem +#define PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) BOOST_PP_SEQ_FOR_EACH_I(PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) +#define PRINT_CONFIG_CLASS_DERIVED_INITIALIZER_ITEM(r, VALUE, i, elem) BOOST_PP_COMMA_IF(i) elem(VALUE) +#define PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, VALUE) BOOST_PP_SEQ_FOR_EACH_I(PRINT_CONFIG_CLASS_DERIVED_INITIALIZER_ITEM, VALUE, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) +#define PRINT_CONFIG_CLASS_DERIVED_INITCACHE_ITEM(r, data, elem) this->elem::initialize(cache, base_ptr); +#define PRINT_CONFIG_CLASS_DERIVED_INITCACHE(CLASSES_PARENTS_TUPLE) BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_INITCACHE_ITEM, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) +#define PRINT_CONFIG_CLASS_DERIVED_HASH(r, data, elem) boost::hash_combine(seed, static_cast(this)->hash()); +#define PRINT_CONFIG_CLASS_DERIVED_EQUAL(r, data, elem) \ + if (! (*static_cast(this) == static_cast(rhs))) return false; + +// Generic version, with or without new parameters. Don't use this directly. +#define PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION, PARAMETER_REGISTRATION, PARAMETER_HASHES, PARAMETER_EQUALS) \ +class CLASS_NAME : PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) { \ + STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ + CLASS_NAME() : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 0) { initialize_cache(); *this = s_cache_##CLASS_NAME.defaults(); } \ +public: \ + PARAMETER_DEFINITION \ + size_t hash() const throw() \ + { \ + size_t seed = 0; \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_HASH, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \ + PARAMETER_HASHES \ + return seed; \ + } \ + bool operator==(const CLASS_NAME &rhs) const throw() \ + { \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_EQUAL, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \ + PARAMETER_EQUALS \ + return true; \ + } \ + bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \ +protected: \ + CLASS_NAME(int) : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 1) {} \ + void initialize(StaticCacheBase &cache, const char* base_ptr) { \ + PRINT_CONFIG_CLASS_DERIVED_INITCACHE(CLASSES_PARENTS_TUPLE) \ + PARAMETER_REGISTRATION \ + } \ +}; +// Variant without adding new parameters. +#define PRINT_CONFIG_CLASS_DERIVED_DEFINE0(CLASS_NAME, CLASSES_PARENTS_TUPLE) \ + PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), BOOST_PP_EMPTY()) +// Variant with adding new parameters. +#define PRINT_CONFIG_CLASS_DERIVED_DEFINE(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION_SEQ) \ + PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ), \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ), \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ), \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ)) // This object is mapped to Perl as Slic3r::Config::PrintObject. -class PrintObjectConfig : public StaticPrintConfig -{ - STATIC_PRINT_CONFIG_CACHE(PrintObjectConfig) -public: - ConfigOptionFloat brim_offset; - ConfigOptionEnum brim_type; - ConfigOptionFloat brim_width; - ConfigOptionBool clip_multipart_objects; - ConfigOptionBool dont_support_bridges; - ConfigOptionFloat elefant_foot_compensation; - ConfigOptionFloatOrPercent extrusion_width; - ConfigOptionBool infill_only_where_needed; - // Force the generation of solid shells between adjacent materials/volumes. - ConfigOptionBool interface_shells; - ConfigOptionFloat layer_height; - ConfigOptionFloat raft_contact_distance; - ConfigOptionFloat raft_expansion; - ConfigOptionPercent raft_first_layer_density; - ConfigOptionFloat raft_first_layer_expansion; - ConfigOptionInt raft_layers; - ConfigOptionEnum seam_position; -// ConfigOptionFloat seam_preferred_direction; -// ConfigOptionFloat seam_preferred_direction_jitter; - ConfigOptionFloat slice_closing_radius; - ConfigOptionBool support_material; - // Automatic supports (generated based on support_material_threshold). - ConfigOptionBool support_material_auto; - // Direction of the support pattern (in XY plane). - ConfigOptionFloat support_material_angle; - ConfigOptionBool support_material_buildplate_only; - ConfigOptionFloat support_material_contact_distance; - ConfigOptionFloat support_material_bottom_contact_distance; - ConfigOptionInt support_material_enforce_layers; - ConfigOptionInt support_material_extruder; - ConfigOptionFloatOrPercent support_material_extrusion_width; - ConfigOptionBool support_material_interface_contact_loops; - ConfigOptionInt support_material_interface_extruder; - ConfigOptionInt support_material_interface_layers; - ConfigOptionInt support_material_bottom_interface_layers; - // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. - ConfigOptionFloat support_material_interface_spacing; - ConfigOptionFloatOrPercent support_material_interface_speed; - ConfigOptionEnum support_material_pattern; - ConfigOptionEnum support_material_interface_pattern; - // Morphological closing of support areas. Only used for "sung" supports. - ConfigOptionFloat support_material_closing_radius; - // Spacing between support material lines (the hatching distance). - ConfigOptionFloat support_material_spacing; - ConfigOptionFloat support_material_speed; - ConfigOptionEnum support_material_style; - ConfigOptionBool support_material_synchronize_layers; - // Overhang angle threshold. - ConfigOptionInt support_material_threshold; - ConfigOptionBool support_material_with_sheath; - ConfigOptionFloatOrPercent support_material_xy_spacing; - ConfigOptionBool thick_bridges; - ConfigOptionFloat xy_size_compensation; - ConfigOptionBool wipe_into_objects; +PRINT_CONFIG_CLASS_DEFINE( + PrintObjectConfig, -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(brim_offset); - OPT_PTR(brim_type); - OPT_PTR(brim_width); - OPT_PTR(clip_multipart_objects); - OPT_PTR(dont_support_bridges); - OPT_PTR(elefant_foot_compensation); - OPT_PTR(extrusion_width); - OPT_PTR(infill_only_where_needed); - OPT_PTR(interface_shells); - OPT_PTR(layer_height); - OPT_PTR(raft_contact_distance); - OPT_PTR(raft_expansion); - OPT_PTR(raft_first_layer_density); - OPT_PTR(raft_first_layer_expansion); - OPT_PTR(raft_layers); - OPT_PTR(seam_position); - OPT_PTR(slice_closing_radius); -// OPT_PTR(seam_preferred_direction); -// OPT_PTR(seam_preferred_direction_jitter); - OPT_PTR(support_material); - OPT_PTR(support_material_auto); - OPT_PTR(support_material_angle); - OPT_PTR(support_material_buildplate_only); - OPT_PTR(support_material_contact_distance); - OPT_PTR(support_material_bottom_contact_distance); - OPT_PTR(support_material_enforce_layers); - OPT_PTR(support_material_interface_contact_loops); - OPT_PTR(support_material_extruder); - OPT_PTR(support_material_extrusion_width); - OPT_PTR(support_material_interface_extruder); - OPT_PTR(support_material_interface_layers); - OPT_PTR(support_material_bottom_interface_layers); - OPT_PTR(support_material_closing_radius); - OPT_PTR(support_material_interface_spacing); - OPT_PTR(support_material_interface_speed); - OPT_PTR(support_material_pattern); - OPT_PTR(support_material_interface_pattern); - OPT_PTR(support_material_spacing); - OPT_PTR(support_material_speed); - OPT_PTR(support_material_style); - OPT_PTR(support_material_synchronize_layers); - OPT_PTR(support_material_xy_spacing); - OPT_PTR(support_material_threshold); - OPT_PTR(support_material_with_sheath); - OPT_PTR(thick_bridges); - OPT_PTR(xy_size_compensation); - OPT_PTR(wipe_into_objects); - } -}; + ((ConfigOptionFloat, brim_offset)) + ((ConfigOptionEnum, brim_type)) + ((ConfigOptionFloat, brim_width)) + ((ConfigOptionBool, clip_multipart_objects)) + ((ConfigOptionBool, dont_support_bridges)) + ((ConfigOptionFloat, elefant_foot_compensation)) + ((ConfigOptionFloatOrPercent, extrusion_width)) + ((ConfigOptionBool, infill_only_where_needed)) + // Force the generation of solid shells between adjacent materials/volumes. + ((ConfigOptionBool, interface_shells)) + ((ConfigOptionFloat, layer_height)) + ((ConfigOptionFloat, raft_contact_distance)) + ((ConfigOptionFloat, raft_expansion)) + ((ConfigOptionPercent, raft_first_layer_density)) + ((ConfigOptionFloat, raft_first_layer_expansion)) + ((ConfigOptionInt, raft_layers)) + ((ConfigOptionEnum, seam_position)) +// ((ConfigOptionFloat, seam_preferred_direction)) +// ((ConfigOptionFloat, seam_preferred_direction_jitter)) + ((ConfigOptionFloat, slice_closing_radius)) + ((ConfigOptionBool, support_material)) + // Automatic supports (generated based on support_material_threshold). + ((ConfigOptionBool, support_material_auto)) + // Direction of the support pattern (in XY plane).` + ((ConfigOptionFloat, support_material_angle)) + ((ConfigOptionBool, support_material_buildplate_only)) + ((ConfigOptionFloat, support_material_contact_distance)) + ((ConfigOptionFloat, support_material_bottom_contact_distance)) + ((ConfigOptionInt, support_material_enforce_layers)) + ((ConfigOptionInt, support_material_extruder)) + ((ConfigOptionFloatOrPercent, support_material_extrusion_width)) + ((ConfigOptionBool, support_material_interface_contact_loops)) + ((ConfigOptionInt, support_material_interface_extruder)) + ((ConfigOptionInt, support_material_interface_layers)) + ((ConfigOptionInt, support_material_bottom_interface_layers)) + // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. + ((ConfigOptionFloat, support_material_interface_spacing)) + ((ConfigOptionFloatOrPercent, support_material_interface_speed)) + ((ConfigOptionEnum, support_material_pattern)) + ((ConfigOptionEnum, support_material_interface_pattern)) + // Morphological closing of support areas. Only used for "sung" supports. + ((ConfigOptionFloat, support_material_closing_radius)) + // Spacing between support material lines (the hatching distance). + ((ConfigOptionFloat, support_material_spacing)) + ((ConfigOptionFloat, support_material_speed)) + ((ConfigOptionEnum, support_material_style)) + ((ConfigOptionBool, support_material_synchronize_layers)) + // Overhang angle threshold. + ((ConfigOptionInt, support_material_threshold)) + ((ConfigOptionBool, support_material_with_sheath)) + ((ConfigOptionFloatOrPercent, support_material_xy_spacing)) + ((ConfigOptionBool, thick_bridges)) + ((ConfigOptionFloat, xy_size_compensation)) + ((ConfigOptionBool, wipe_into_objects)) +) // This object is mapped to Perl as Slic3r::Config::PrintRegion. -class PrintRegionConfig : public StaticPrintConfig -{ - STATIC_PRINT_CONFIG_CACHE(PrintRegionConfig) -public: - ConfigOptionFloat bridge_angle; - ConfigOptionInt bottom_solid_layers; - ConfigOptionFloat bottom_solid_min_thickness; - ConfigOptionFloat bridge_flow_ratio; - ConfigOptionFloat bridge_speed; - ConfigOptionBool ensure_vertical_shell_thickness; - ConfigOptionEnum top_fill_pattern; - ConfigOptionEnum bottom_fill_pattern; - ConfigOptionFloatOrPercent external_perimeter_extrusion_width; - ConfigOptionFloatOrPercent external_perimeter_speed; - ConfigOptionBool external_perimeters_first; - ConfigOptionBool extra_perimeters; - ConfigOptionFloat fill_angle; - ConfigOptionPercent fill_density; - ConfigOptionEnum fill_pattern; - ConfigOptionEnum fuzzy_skin; - ConfigOptionFloat fuzzy_skin_thickness; - ConfigOptionFloat fuzzy_skin_point_dist; - ConfigOptionBool gap_fill_enabled; - ConfigOptionFloat gap_fill_speed; - ConfigOptionFloatOrPercent infill_anchor; - ConfigOptionFloatOrPercent infill_anchor_max; - ConfigOptionInt infill_extruder; - ConfigOptionFloatOrPercent infill_extrusion_width; - ConfigOptionInt infill_every_layers; - ConfigOptionFloatOrPercent infill_overlap; - ConfigOptionFloat infill_speed; +PRINT_CONFIG_CLASS_DEFINE( + PrintRegionConfig, + + ((ConfigOptionFloat, bridge_angle)) + ((ConfigOptionInt, bottom_solid_layers)) + ((ConfigOptionFloat, bottom_solid_min_thickness)) + ((ConfigOptionFloat, bridge_flow_ratio)) + ((ConfigOptionFloat, bridge_speed)) + ((ConfigOptionBool, ensure_vertical_shell_thickness)) + ((ConfigOptionEnum, top_fill_pattern)) + ((ConfigOptionEnum, bottom_fill_pattern)) + ((ConfigOptionFloatOrPercent, external_perimeter_extrusion_width)) + ((ConfigOptionFloatOrPercent, external_perimeter_speed)) + ((ConfigOptionBool, external_perimeters_first)) + ((ConfigOptionBool, extra_perimeters)) + ((ConfigOptionFloat, fill_angle)) + ((ConfigOptionPercent, fill_density)) + ((ConfigOptionEnum, fill_pattern)) + ((ConfigOptionEnum, fuzzy_skin)) + ((ConfigOptionFloat, fuzzy_skin_thickness)) + ((ConfigOptionFloat, fuzzy_skin_point_dist)) + ((ConfigOptionBool, gap_fill_enabled)) + ((ConfigOptionFloat, gap_fill_speed)) + ((ConfigOptionFloatOrPercent, infill_anchor)) + ((ConfigOptionFloatOrPercent, infill_anchor_max)) + ((ConfigOptionInt, infill_extruder)) + ((ConfigOptionFloatOrPercent, infill_extrusion_width)) + ((ConfigOptionInt, infill_every_layers)) + ((ConfigOptionFloatOrPercent, infill_overlap)) + ((ConfigOptionFloat, infill_speed)) // Ironing options - ConfigOptionBool ironing; - ConfigOptionEnum ironing_type; - ConfigOptionPercent ironing_flowrate; - ConfigOptionFloat ironing_spacing; - ConfigOptionFloat ironing_speed; + ((ConfigOptionBool, ironing)) + ((ConfigOptionEnum, ironing_type)) + ((ConfigOptionPercent, ironing_flowrate)) + ((ConfigOptionFloat, ironing_spacing)) + ((ConfigOptionFloat, ironing_speed)) // Detect bridging perimeters - ConfigOptionBool overhangs; - ConfigOptionInt perimeter_extruder; - ConfigOptionFloatOrPercent perimeter_extrusion_width; - ConfigOptionFloat perimeter_speed; + ((ConfigOptionBool, overhangs)) + ((ConfigOptionInt, perimeter_extruder)) + ((ConfigOptionFloatOrPercent, perimeter_extrusion_width)) + ((ConfigOptionFloat, perimeter_speed)) // Total number of perimeters. - ConfigOptionInt perimeters; - ConfigOptionFloatOrPercent small_perimeter_speed; - ConfigOptionFloat solid_infill_below_area; - ConfigOptionInt solid_infill_extruder; - ConfigOptionFloatOrPercent solid_infill_extrusion_width; - ConfigOptionInt solid_infill_every_layers; - ConfigOptionFloatOrPercent solid_infill_speed; + ((ConfigOptionInt, perimeters)) + ((ConfigOptionFloatOrPercent, small_perimeter_speed)) + ((ConfigOptionFloat, solid_infill_below_area)) + ((ConfigOptionInt, solid_infill_extruder)) + ((ConfigOptionFloatOrPercent, solid_infill_extrusion_width)) + ((ConfigOptionInt, solid_infill_every_layers)) + ((ConfigOptionFloatOrPercent, solid_infill_speed)) // Detect thin walls. - ConfigOptionBool thin_walls; - ConfigOptionFloatOrPercent top_infill_extrusion_width; - ConfigOptionInt top_solid_layers; - ConfigOptionFloat top_solid_min_thickness; - ConfigOptionFloatOrPercent top_solid_infill_speed; - ConfigOptionBool wipe_into_infill; + ((ConfigOptionBool, thin_walls)) + ((ConfigOptionFloatOrPercent, top_infill_extrusion_width)) + ((ConfigOptionInt, top_solid_layers)) + ((ConfigOptionFloat, top_solid_min_thickness)) + ((ConfigOptionFloatOrPercent, top_solid_infill_speed)) + ((ConfigOptionBool, wipe_into_infill)) +) -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(bridge_angle); - OPT_PTR(bottom_solid_layers); - OPT_PTR(bottom_solid_min_thickness); - OPT_PTR(bridge_flow_ratio); - OPT_PTR(bridge_speed); - OPT_PTR(ensure_vertical_shell_thickness); - OPT_PTR(top_fill_pattern); - OPT_PTR(bottom_fill_pattern); - OPT_PTR(external_perimeter_extrusion_width); - OPT_PTR(external_perimeter_speed); - OPT_PTR(external_perimeters_first); - OPT_PTR(extra_perimeters); - OPT_PTR(fill_angle); - OPT_PTR(fill_density); - OPT_PTR(fill_pattern); - OPT_PTR(fuzzy_skin); - OPT_PTR(fuzzy_skin_thickness); - OPT_PTR(fuzzy_skin_point_dist); - OPT_PTR(gap_fill_enabled); - OPT_PTR(gap_fill_speed); - OPT_PTR(infill_anchor); - OPT_PTR(infill_anchor_max); - OPT_PTR(infill_extruder); - OPT_PTR(infill_extrusion_width); - OPT_PTR(infill_every_layers); - OPT_PTR(infill_overlap); - OPT_PTR(infill_speed); - OPT_PTR(ironing); - OPT_PTR(ironing_type); - OPT_PTR(ironing_flowrate); - OPT_PTR(ironing_spacing); - OPT_PTR(ironing_speed); - OPT_PTR(overhangs); - OPT_PTR(perimeter_extruder); - OPT_PTR(perimeter_extrusion_width); - OPT_PTR(perimeter_speed); - OPT_PTR(perimeters); - OPT_PTR(small_perimeter_speed); - OPT_PTR(solid_infill_below_area); - OPT_PTR(solid_infill_extruder); - OPT_PTR(solid_infill_extrusion_width); - OPT_PTR(solid_infill_every_layers); - OPT_PTR(solid_infill_speed); - OPT_PTR(thin_walls); - OPT_PTR(top_infill_extrusion_width); - OPT_PTR(top_solid_infill_speed); - OPT_PTR(top_solid_layers); - OPT_PTR(top_solid_min_thickness); - OPT_PTR(wipe_into_infill); - } -}; +PRINT_CONFIG_CLASS_DEFINE( + MachineEnvelopeConfig, -class MachineEnvelopeConfig : public StaticPrintConfig -{ - STATIC_PRINT_CONFIG_CACHE(MachineEnvelopeConfig) -public: - // Allowing the machine limits to be completely ignored or used just for time estimator. - ConfigOptionEnum machine_limits_usage; + // Allowing the machine limits to be completely ignored or used just for time estimator. + ((ConfigOptionEnum, machine_limits_usage)) // M201 X... Y... Z... E... [mm/sec^2] - ConfigOptionFloats machine_max_acceleration_x; - ConfigOptionFloats machine_max_acceleration_y; - ConfigOptionFloats machine_max_acceleration_z; - ConfigOptionFloats machine_max_acceleration_e; + ((ConfigOptionFloats, machine_max_acceleration_x)) + ((ConfigOptionFloats, machine_max_acceleration_y)) + ((ConfigOptionFloats, machine_max_acceleration_z)) + ((ConfigOptionFloats, machine_max_acceleration_e)) // M203 X... Y... Z... E... [mm/sec] - ConfigOptionFloats machine_max_feedrate_x; - ConfigOptionFloats machine_max_feedrate_y; - ConfigOptionFloats machine_max_feedrate_z; - ConfigOptionFloats machine_max_feedrate_e; + ((ConfigOptionFloats, machine_max_feedrate_x)) + ((ConfigOptionFloats, machine_max_feedrate_y)) + ((ConfigOptionFloats, machine_max_feedrate_z)) + ((ConfigOptionFloats, machine_max_feedrate_e)) // M204 P... R... T...[mm/sec^2] - ConfigOptionFloats machine_max_acceleration_extruding; - ConfigOptionFloats machine_max_acceleration_retracting; - ConfigOptionFloats machine_max_acceleration_travel; + ((ConfigOptionFloats, machine_max_acceleration_extruding)) + ((ConfigOptionFloats, machine_max_acceleration_retracting)) + ((ConfigOptionFloats, machine_max_acceleration_travel)) // M205 X... Y... Z... E... [mm/sec] - ConfigOptionFloats machine_max_jerk_x; - ConfigOptionFloats machine_max_jerk_y; - ConfigOptionFloats machine_max_jerk_z; - ConfigOptionFloats machine_max_jerk_e; + ((ConfigOptionFloats, machine_max_jerk_x)) + ((ConfigOptionFloats, machine_max_jerk_y)) + ((ConfigOptionFloats, machine_max_jerk_z)) + ((ConfigOptionFloats, machine_max_jerk_e)) // M205 T... [mm/sec] - ConfigOptionFloats machine_min_travel_rate; + ((ConfigOptionFloats, machine_min_travel_rate)) // M205 S... [mm/sec] - ConfigOptionFloats machine_min_extruding_rate; - -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(machine_limits_usage); - OPT_PTR(machine_max_acceleration_x); - OPT_PTR(machine_max_acceleration_y); - OPT_PTR(machine_max_acceleration_z); - OPT_PTR(machine_max_acceleration_e); - OPT_PTR(machine_max_feedrate_x); - OPT_PTR(machine_max_feedrate_y); - OPT_PTR(machine_max_feedrate_z); - OPT_PTR(machine_max_feedrate_e); - OPT_PTR(machine_max_acceleration_extruding); - OPT_PTR(machine_max_acceleration_retracting); - OPT_PTR(machine_max_acceleration_travel); - OPT_PTR(machine_max_jerk_x); - OPT_PTR(machine_max_jerk_y); - OPT_PTR(machine_max_jerk_z); - OPT_PTR(machine_max_jerk_e); - OPT_PTR(machine_min_travel_rate); - OPT_PTR(machine_min_extruding_rate); - } -}; + ((ConfigOptionFloats, machine_min_extruding_rate)) +) // This object is mapped to Perl as Slic3r::Config::GCode. -class GCodeConfig : public StaticPrintConfig +PRINT_CONFIG_CLASS_DEFINE( + GCodeConfig, + + ((ConfigOptionString, before_layer_gcode)) + ((ConfigOptionString, between_objects_gcode)) + ((ConfigOptionFloats, deretract_speed)) + ((ConfigOptionString, end_gcode)) + ((ConfigOptionStrings, end_filament_gcode)) + ((ConfigOptionString, extrusion_axis)) + ((ConfigOptionFloats, extrusion_multiplier)) + ((ConfigOptionFloats, filament_diameter)) + ((ConfigOptionFloats, filament_density)) + ((ConfigOptionStrings, filament_type)) + ((ConfigOptionBools, filament_soluble)) + ((ConfigOptionFloats, filament_cost)) + ((ConfigOptionFloats, filament_spool_weight)) + ((ConfigOptionFloats, filament_max_volumetric_speed)) + ((ConfigOptionFloats, filament_loading_speed)) + ((ConfigOptionFloats, filament_loading_speed_start)) + ((ConfigOptionFloats, filament_load_time)) + ((ConfigOptionFloats, filament_unloading_speed)) + ((ConfigOptionFloats, filament_unloading_speed_start)) + ((ConfigOptionFloats, filament_toolchange_delay)) + ((ConfigOptionFloats, filament_unload_time)) + ((ConfigOptionInts, filament_cooling_moves)) + ((ConfigOptionFloats, filament_cooling_initial_speed)) + ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) + ((ConfigOptionFloats, filament_cooling_final_speed)) + ((ConfigOptionStrings, filament_ramming_parameters)) + ((ConfigOptionBool, gcode_comments)) + ((ConfigOptionEnum, gcode_flavor)) + ((ConfigOptionBool, gcode_label_objects)) + ((ConfigOptionString, layer_gcode)) + ((ConfigOptionFloat, max_print_speed)) + ((ConfigOptionFloat, max_volumetric_speed)) +//#ifdef HAS_PRESSURE_EQUALIZER +// ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_positive)) +// ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_negative)) +//#endif + ((ConfigOptionPercents, retract_before_wipe)) + ((ConfigOptionFloats, retract_length)) + ((ConfigOptionFloats, retract_length_toolchange)) + ((ConfigOptionFloats, retract_lift)) + ((ConfigOptionFloats, retract_lift_above)) + ((ConfigOptionFloats, retract_lift_below)) + ((ConfigOptionFloats, retract_restart_extra)) + ((ConfigOptionFloats, retract_restart_extra_toolchange)) + ((ConfigOptionFloats, retract_speed)) + ((ConfigOptionString, start_gcode)) + ((ConfigOptionStrings, start_filament_gcode)) + ((ConfigOptionBool, single_extruder_multi_material)) + ((ConfigOptionBool, single_extruder_multi_material_priming)) + ((ConfigOptionBool, wipe_tower_no_sparse_layers)) + ((ConfigOptionString, toolchange_gcode)) + ((ConfigOptionFloat, travel_speed)) + ((ConfigOptionBool, use_firmware_retraction)) + ((ConfigOptionBool, use_relative_e_distances)) + ((ConfigOptionBool, use_volumetric_e)) + ((ConfigOptionBool, variable_layer_height)) + ((ConfigOptionFloat, cooling_tube_retraction)) + ((ConfigOptionFloat, cooling_tube_length)) + ((ConfigOptionBool, high_current_on_filament_swap)) + ((ConfigOptionFloat, parking_pos_retraction)) + ((ConfigOptionBool, remaining_times)) + ((ConfigOptionBool, silent_mode)) + ((ConfigOptionFloat, extra_loading_move)) + ((ConfigOptionString, color_change_gcode)) + ((ConfigOptionString, pause_print_gcode)) + ((ConfigOptionString, template_custom_gcode)) +) + +static inline std::string get_extrusion_axis(const GCodeConfig &cfg) { - STATIC_PRINT_CONFIG_CACHE(GCodeConfig) -public: - ConfigOptionString before_layer_gcode; - ConfigOptionString between_objects_gcode; - ConfigOptionFloats deretract_speed; - ConfigOptionString end_gcode; - ConfigOptionStrings end_filament_gcode; - ConfigOptionString extrusion_axis; - ConfigOptionFloats extrusion_multiplier; - ConfigOptionFloats filament_diameter; - ConfigOptionFloats filament_density; - ConfigOptionStrings filament_type; - ConfigOptionBools filament_soluble; - ConfigOptionFloats filament_cost; - ConfigOptionFloats filament_spool_weight; - ConfigOptionFloats filament_max_volumetric_speed; - ConfigOptionFloats filament_loading_speed; - ConfigOptionFloats filament_loading_speed_start; - ConfigOptionFloats filament_load_time; - ConfigOptionFloats filament_unloading_speed; - ConfigOptionFloats filament_unloading_speed_start; - ConfigOptionFloats filament_toolchange_delay; - ConfigOptionFloats filament_unload_time; - ConfigOptionInts filament_cooling_moves; - ConfigOptionFloats filament_cooling_initial_speed; - ConfigOptionFloats filament_minimal_purge_on_wipe_tower; - ConfigOptionFloats filament_cooling_final_speed; - ConfigOptionStrings filament_ramming_parameters; - ConfigOptionBool gcode_comments; - ConfigOptionEnum gcode_flavor; - ConfigOptionBool gcode_label_objects; - ConfigOptionString layer_gcode; - ConfigOptionFloat max_print_speed; - ConfigOptionFloat max_volumetric_speed; -#ifdef HAS_PRESSURE_EQUALIZER - ConfigOptionFloat max_volumetric_extrusion_rate_slope_positive; - ConfigOptionFloat max_volumetric_extrusion_rate_slope_negative; -#endif - ConfigOptionPercents retract_before_wipe; - ConfigOptionFloats retract_length; - ConfigOptionFloats retract_length_toolchange; - ConfigOptionFloats retract_lift; - ConfigOptionFloats retract_lift_above; - ConfigOptionFloats retract_lift_below; - ConfigOptionFloats retract_restart_extra; - ConfigOptionFloats retract_restart_extra_toolchange; - ConfigOptionFloats retract_speed; - ConfigOptionString start_gcode; - ConfigOptionStrings start_filament_gcode; - ConfigOptionBool single_extruder_multi_material; - ConfigOptionBool single_extruder_multi_material_priming; - ConfigOptionBool wipe_tower_no_sparse_layers; - ConfigOptionString toolchange_gcode; - ConfigOptionFloat travel_speed; - ConfigOptionBool use_firmware_retraction; - ConfigOptionBool use_relative_e_distances; - ConfigOptionBool use_volumetric_e; - ConfigOptionBool variable_layer_height; - ConfigOptionFloat cooling_tube_retraction; - ConfigOptionFloat cooling_tube_length; - ConfigOptionBool high_current_on_filament_swap; - ConfigOptionFloat parking_pos_retraction; - ConfigOptionBool remaining_times; - ConfigOptionBool silent_mode; - ConfigOptionFloat extra_loading_move; - ConfigOptionString color_change_gcode; - ConfigOptionString pause_print_gcode; - ConfigOptionString template_custom_gcode; - - std::string get_extrusion_axis() const - { - return - ((this->gcode_flavor.value == gcfMach3) || (this->gcode_flavor.value == gcfMachinekit)) ? "A" : - (this->gcode_flavor.value == gcfNoExtrusion) ? "" : this->extrusion_axis.value; - } - -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(before_layer_gcode); - OPT_PTR(between_objects_gcode); - OPT_PTR(deretract_speed); - OPT_PTR(end_gcode); - OPT_PTR(end_filament_gcode); - OPT_PTR(extrusion_axis); - OPT_PTR(extrusion_multiplier); - OPT_PTR(filament_diameter); - OPT_PTR(filament_density); - OPT_PTR(filament_type); - OPT_PTR(filament_soluble); - OPT_PTR(filament_cost); - OPT_PTR(filament_spool_weight); - OPT_PTR(filament_max_volumetric_speed); - OPT_PTR(filament_loading_speed); - OPT_PTR(filament_loading_speed_start); - OPT_PTR(filament_load_time); - OPT_PTR(filament_unloading_speed); - OPT_PTR(filament_unloading_speed_start); - OPT_PTR(filament_unload_time); - OPT_PTR(filament_toolchange_delay); - OPT_PTR(filament_cooling_moves); - OPT_PTR(filament_cooling_initial_speed); - OPT_PTR(filament_minimal_purge_on_wipe_tower); - OPT_PTR(filament_cooling_final_speed); - OPT_PTR(filament_ramming_parameters); - OPT_PTR(gcode_comments); - OPT_PTR(gcode_flavor); - OPT_PTR(gcode_label_objects); - OPT_PTR(layer_gcode); - OPT_PTR(max_print_speed); - OPT_PTR(max_volumetric_speed); -#ifdef HAS_PRESSURE_EQUALIZER - OPT_PTR(max_volumetric_extrusion_rate_slope_positive); - OPT_PTR(max_volumetric_extrusion_rate_slope_negative); -#endif /* HAS_PRESSURE_EQUALIZER */ - OPT_PTR(retract_before_wipe); - OPT_PTR(retract_length); - OPT_PTR(retract_length_toolchange); - OPT_PTR(retract_lift); - OPT_PTR(retract_lift_above); - OPT_PTR(retract_lift_below); - OPT_PTR(retract_restart_extra); - OPT_PTR(retract_restart_extra_toolchange); - OPT_PTR(retract_speed); - OPT_PTR(single_extruder_multi_material); - OPT_PTR(single_extruder_multi_material_priming); - OPT_PTR(wipe_tower_no_sparse_layers); - OPT_PTR(start_gcode); - OPT_PTR(start_filament_gcode); - OPT_PTR(toolchange_gcode); - OPT_PTR(travel_speed); - OPT_PTR(use_firmware_retraction); - OPT_PTR(use_relative_e_distances); - OPT_PTR(use_volumetric_e); - OPT_PTR(variable_layer_height); - OPT_PTR(cooling_tube_retraction); - OPT_PTR(cooling_tube_length); - OPT_PTR(high_current_on_filament_swap); - OPT_PTR(parking_pos_retraction); - OPT_PTR(remaining_times); - OPT_PTR(silent_mode); - OPT_PTR(extra_loading_move); - OPT_PTR(color_change_gcode); - OPT_PTR(pause_print_gcode); - OPT_PTR(template_custom_gcode); - } -}; + return + ((cfg.gcode_flavor.value == gcfMach3) || (cfg.gcode_flavor.value == gcfMachinekit)) ? "A" : + (cfg.gcode_flavor.value == gcfNoExtrusion) ? "" : cfg.extrusion_axis.value; +} // This object is mapped to Perl as Slic3r::Config::Print. -class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig -{ - STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) - PrintConfig() : MachineEnvelopeConfig(0), GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } -public: +PRINT_CONFIG_CLASS_DERIVED_DEFINE( + PrintConfig, + (MachineEnvelopeConfig, GCodeConfig), - ConfigOptionBool avoid_crossing_perimeters; - ConfigOptionFloatOrPercent avoid_crossing_perimeters_max_detour; - ConfigOptionPoints bed_shape; - ConfigOptionInts bed_temperature; - ConfigOptionFloat bridge_acceleration; - ConfigOptionInts bridge_fan_speed; - ConfigOptionBool complete_objects; - ConfigOptionFloats colorprint_heights; - ConfigOptionBools cooling; - ConfigOptionFloat default_acceleration; - ConfigOptionInts disable_fan_first_layers; - ConfigOptionFloat duplicate_distance; - ConfigOptionFloat extruder_clearance_height; - ConfigOptionFloat extruder_clearance_radius; - ConfigOptionStrings extruder_colour; - ConfigOptionPoints extruder_offset; - ConfigOptionBools fan_always_on; - ConfigOptionInts fan_below_layer_time; - ConfigOptionStrings filament_colour; - ConfigOptionStrings filament_notes; - ConfigOptionFloat first_layer_acceleration; - ConfigOptionInts first_layer_bed_temperature; - ConfigOptionFloatOrPercent first_layer_extrusion_width; - ConfigOptionFloatOrPercent first_layer_height; - ConfigOptionFloatOrPercent first_layer_speed; - ConfigOptionInts first_layer_temperature; - ConfigOptionInts full_fan_speed_layer; - ConfigOptionFloat infill_acceleration; - ConfigOptionBool infill_first; - ConfigOptionInts max_fan_speed; - ConfigOptionFloats max_layer_height; - ConfigOptionInts min_fan_speed; - ConfigOptionFloats min_layer_height; - ConfigOptionFloat max_print_height; - ConfigOptionFloats min_print_speed; - ConfigOptionFloat min_skirt_length; - ConfigOptionString notes; - ConfigOptionFloats nozzle_diameter; - ConfigOptionBool only_retract_when_crossing_perimeters; - ConfigOptionBool ooze_prevention; - ConfigOptionString output_filename_format; - ConfigOptionFloat perimeter_acceleration; - ConfigOptionStrings post_process; - ConfigOptionString printer_model; - ConfigOptionString printer_notes; - ConfigOptionFloat resolution; - ConfigOptionFloats retract_before_travel; - ConfigOptionBools retract_layer_change; - ConfigOptionFloat skirt_distance; - ConfigOptionInt skirt_height; - ConfigOptionBool draft_shield; - ConfigOptionInt skirts; - ConfigOptionInts slowdown_below_layer_time; - ConfigOptionBool spiral_vase; - ConfigOptionInt standby_temperature_delta; - ConfigOptionInts temperature; - ConfigOptionInt threads; - ConfigOptionBools wipe; - ConfigOptionBool wipe_tower; - ConfigOptionFloat wipe_tower_x; - ConfigOptionFloat wipe_tower_y; - ConfigOptionFloat wipe_tower_width; - ConfigOptionFloat wipe_tower_per_color_wipe; - ConfigOptionFloat wipe_tower_rotation_angle; - ConfigOptionFloat wipe_tower_brim_width; - ConfigOptionFloat wipe_tower_bridging; - ConfigOptionFloats wiping_volumes_matrix; - ConfigOptionFloats wiping_volumes_extruders; - ConfigOptionFloat z_offset; - -protected: - PrintConfig(int) : MachineEnvelopeConfig(1), GCodeConfig(1) {} - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - this->MachineEnvelopeConfig::initialize(cache, base_ptr); - this->GCodeConfig::initialize(cache, base_ptr); - OPT_PTR(avoid_crossing_perimeters); - OPT_PTR(avoid_crossing_perimeters_max_detour); - OPT_PTR(bed_shape); - OPT_PTR(bed_temperature); - OPT_PTR(bridge_acceleration); - OPT_PTR(bridge_fan_speed); - OPT_PTR(complete_objects); - OPT_PTR(colorprint_heights); - OPT_PTR(cooling); - OPT_PTR(default_acceleration); - OPT_PTR(disable_fan_first_layers); - OPT_PTR(duplicate_distance); - OPT_PTR(extruder_clearance_height); - OPT_PTR(extruder_clearance_radius); - OPT_PTR(extruder_colour); - OPT_PTR(extruder_offset); - OPT_PTR(fan_always_on); - OPT_PTR(fan_below_layer_time); - OPT_PTR(filament_colour); - OPT_PTR(filament_notes); - OPT_PTR(first_layer_acceleration); - OPT_PTR(first_layer_bed_temperature); - OPT_PTR(first_layer_extrusion_width); - OPT_PTR(first_layer_height); - OPT_PTR(first_layer_speed); - OPT_PTR(first_layer_temperature); - OPT_PTR(full_fan_speed_layer); - OPT_PTR(infill_acceleration); - OPT_PTR(infill_first); - OPT_PTR(max_fan_speed); - OPT_PTR(max_layer_height); - OPT_PTR(min_fan_speed); - OPT_PTR(min_layer_height); - OPT_PTR(max_print_height); - OPT_PTR(min_print_speed); - OPT_PTR(min_skirt_length); - OPT_PTR(notes); - OPT_PTR(nozzle_diameter); - OPT_PTR(only_retract_when_crossing_perimeters); - OPT_PTR(ooze_prevention); - OPT_PTR(output_filename_format); - OPT_PTR(perimeter_acceleration); - OPT_PTR(post_process); - OPT_PTR(printer_model); - OPT_PTR(printer_notes); - OPT_PTR(resolution); - OPT_PTR(retract_before_travel); - OPT_PTR(retract_layer_change); - OPT_PTR(skirt_distance); - OPT_PTR(skirt_height); - OPT_PTR(draft_shield); - OPT_PTR(skirts); - OPT_PTR(slowdown_below_layer_time); - OPT_PTR(spiral_vase); - OPT_PTR(standby_temperature_delta); - OPT_PTR(temperature); - OPT_PTR(threads); - OPT_PTR(wipe); - OPT_PTR(wipe_tower); - OPT_PTR(wipe_tower_x); - OPT_PTR(wipe_tower_y); - OPT_PTR(wipe_tower_width); - OPT_PTR(wipe_tower_per_color_wipe); - OPT_PTR(wipe_tower_rotation_angle); - OPT_PTR(wipe_tower_brim_width); - OPT_PTR(wipe_tower_bridging); - OPT_PTR(wiping_volumes_matrix); - OPT_PTR(wiping_volumes_extruders); - OPT_PTR(z_offset); - } -}; + ((ConfigOptionBool, avoid_crossing_perimeters)) + ((ConfigOptionFloatOrPercent, avoid_crossing_perimeters_max_detour)) + ((ConfigOptionPoints, bed_shape)) + ((ConfigOptionInts, bed_temperature)) + ((ConfigOptionFloat, bridge_acceleration)) + ((ConfigOptionInts, bridge_fan_speed)) + ((ConfigOptionBool, complete_objects)) + ((ConfigOptionFloats, colorprint_heights)) + ((ConfigOptionBools, cooling)) + ((ConfigOptionFloat, default_acceleration)) + ((ConfigOptionInts, disable_fan_first_layers)) + ((ConfigOptionFloat, duplicate_distance)) + ((ConfigOptionFloat, extruder_clearance_height)) + ((ConfigOptionFloat, extruder_clearance_radius)) + ((ConfigOptionStrings, extruder_colour)) + ((ConfigOptionPoints, extruder_offset)) + ((ConfigOptionBools, fan_always_on)) + ((ConfigOptionInts, fan_below_layer_time)) + ((ConfigOptionStrings, filament_colour)) + ((ConfigOptionStrings, filament_notes)) + ((ConfigOptionFloat, first_layer_acceleration)) + ((ConfigOptionInts, first_layer_bed_temperature)) + ((ConfigOptionFloatOrPercent, first_layer_extrusion_width)) + ((ConfigOptionFloatOrPercent, first_layer_height)) + ((ConfigOptionFloatOrPercent, first_layer_speed)) + ((ConfigOptionInts, first_layer_temperature)) + ((ConfigOptionInts, full_fan_speed_layer)) + ((ConfigOptionFloat, infill_acceleration)) + ((ConfigOptionBool, infill_first)) + ((ConfigOptionInts, max_fan_speed)) + ((ConfigOptionFloats, max_layer_height)) + ((ConfigOptionInts, min_fan_speed)) + ((ConfigOptionFloats, min_layer_height)) + ((ConfigOptionFloat, max_print_height)) + ((ConfigOptionFloats, min_print_speed)) + ((ConfigOptionFloat, min_skirt_length)) + ((ConfigOptionString, notes)) + ((ConfigOptionFloats, nozzle_diameter)) + ((ConfigOptionBool, only_retract_when_crossing_perimeters)) + ((ConfigOptionBool, ooze_prevention)) + ((ConfigOptionString, output_filename_format)) + ((ConfigOptionFloat, perimeter_acceleration)) + ((ConfigOptionStrings, post_process)) + ((ConfigOptionString, printer_model)) + ((ConfigOptionString, printer_notes)) + ((ConfigOptionFloat, resolution)) + ((ConfigOptionFloats, retract_before_travel)) + ((ConfigOptionBools, retract_layer_change)) + ((ConfigOptionFloat, skirt_distance)) + ((ConfigOptionInt, skirt_height)) + ((ConfigOptionBool, draft_shield)) + ((ConfigOptionInt, skirts)) + ((ConfigOptionInts, slowdown_below_layer_time)) + ((ConfigOptionBool, spiral_vase)) + ((ConfigOptionInt, standby_temperature_delta)) + ((ConfigOptionInts, temperature)) + ((ConfigOptionInt, threads)) + ((ConfigOptionBools, wipe)) + ((ConfigOptionBool, wipe_tower)) + ((ConfigOptionFloat, wipe_tower_x)) + ((ConfigOptionFloat, wipe_tower_y)) + ((ConfigOptionFloat, wipe_tower_width)) + ((ConfigOptionFloat, wipe_tower_per_color_wipe)) + ((ConfigOptionFloat, wipe_tower_rotation_angle)) + ((ConfigOptionFloat, wipe_tower_brim_width)) + ((ConfigOptionFloat, wipe_tower_bridging)) + ((ConfigOptionFloats, wiping_volumes_matrix)) + ((ConfigOptionFloats, wiping_volumes_extruders)) + ((ConfigOptionFloat, z_offset)) +) // This object is mapped to Perl as Slic3r::Config::Full. -class FullPrintConfig : - public PrintObjectConfig, - public PrintRegionConfig, - public PrintConfig -{ - STATIC_PRINT_CONFIG_CACHE_DERIVED(FullPrintConfig) - FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); } +PRINT_CONFIG_CLASS_DERIVED_DEFINE0( + FullPrintConfig, + (PrintObjectConfig, PrintRegionConfig, PrintConfig) +) -public: - // Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. - std::string validate(); +// Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. +std::string validate(const FullPrintConfig &config); -protected: - // Protected constructor to be called to initialize ConfigCache::m_default. - FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0) {} - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - this->PrintObjectConfig::initialize(cache, base_ptr); - this->PrintRegionConfig::initialize(cache, base_ptr); - this->PrintConfig ::initialize(cache, base_ptr); - } -}; +PRINT_CONFIG_CLASS_DEFINE( + SLAPrintConfig, + ((ConfigOptionString, output_filename_format)) +) -// This object is mapped to Perl as Slic3r::Config::PrintRegion. -class SLAPrintConfig : public StaticPrintConfig -{ - STATIC_PRINT_CONFIG_CACHE(SLAPrintConfig) -public: - ConfigOptionString output_filename_format; +PRINT_CONFIG_CLASS_DEFINE( + SLAPrintObjectConfig, -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(output_filename_format); - } -}; - -class SLAPrintObjectConfig : public StaticPrintConfig -{ - STATIC_PRINT_CONFIG_CACHE(SLAPrintObjectConfig) -public: - ConfigOptionFloat layer_height; + ((ConfigOptionFloat, layer_height)) //Number of the layers needed for the exposure time fade [3;20] - ConfigOptionInt faded_layers /*= 10*/; + ((ConfigOptionInt, faded_layers))/*= 10*/ - ConfigOptionFloat slice_closing_radius; + ((ConfigOptionFloat, slice_closing_radius)) // Enabling or disabling support creation - ConfigOptionBool supports_enable; + ((ConfigOptionBool, supports_enable)) // Diameter in mm of the pointing side of the head. - ConfigOptionFloat support_head_front_diameter /*= 0.2*/; + ((ConfigOptionFloat, support_head_front_diameter))/*= 0.2*/ // How much the pinhead has to penetrate the model surface - ConfigOptionFloat support_head_penetration /*= 0.2*/; + ((ConfigOptionFloat, support_head_penetration))/*= 0.2*/ // Width in mm from the back sphere center to the front sphere center. - ConfigOptionFloat support_head_width /*= 1.0*/; + ((ConfigOptionFloat, support_head_width))/*= 1.0*/ // Radius in mm of the support pillars. - ConfigOptionFloat support_pillar_diameter /*= 0.8*/; + ((ConfigOptionFloat, support_pillar_diameter))/*= 0.8*/ // The percentage of smaller pillars compared to the normal pillar diameter // which are used in problematic areas where a normal pilla cannot fit. - ConfigOptionPercent support_small_pillar_diameter_percent; + ((ConfigOptionPercent, support_small_pillar_diameter_percent)) // How much bridge (supporting another pinhead) can be placed on a pillar. - ConfigOptionInt support_max_bridges_on_pillar; + ((ConfigOptionInt, support_max_bridges_on_pillar)) // How the pillars are bridged together - ConfigOptionEnum support_pillar_connection_mode; + ((ConfigOptionEnum, support_pillar_connection_mode)) // Generate only ground facing supports - ConfigOptionBool support_buildplate_only; + ((ConfigOptionBool, support_buildplate_only)) // TODO: unimplemented at the moment. This coefficient will have an impact // when bridges and pillars are merged. The resulting pillar should be a bit // thicker than the ones merging into it. How much thicker? I don't know // but it will be derived from this value. - ConfigOptionFloat support_pillar_widening_factor; + ((ConfigOptionFloat, support_pillar_widening_factor)) // Radius in mm of the pillar base. - ConfigOptionFloat support_base_diameter /*= 2.0*/; + ((ConfigOptionFloat, support_base_diameter))/*= 2.0*/ // The height of the pillar base cone in mm. - ConfigOptionFloat support_base_height /*= 1.0*/; + ((ConfigOptionFloat, support_base_height))/*= 1.0*/ // The minimum distance of the pillar base from the model in mm. - ConfigOptionFloat support_base_safety_distance; /*= 1.0*/ + ((ConfigOptionFloat, support_base_safety_distance)) /*= 1.0*/ // The default angle for connecting support sticks and junctions. - ConfigOptionFloat support_critical_angle /*= 45*/; + ((ConfigOptionFloat, support_critical_angle))/*= 45*/ // The max length of a bridge in mm - ConfigOptionFloat support_max_bridge_length /*= 15.0*/; + ((ConfigOptionFloat, support_max_bridge_length))/*= 15.0*/ // The max distance of two pillars to get cross linked. - ConfigOptionFloat support_max_pillar_link_distance; + ((ConfigOptionFloat, support_max_pillar_link_distance)) // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. Units in mm. - ConfigOptionFloat support_object_elevation /*= 5.0*/; + ((ConfigOptionFloat, support_object_elevation))/*= 5.0*/ /////// Following options influence automatic support points placement: - ConfigOptionInt support_points_density_relative; - ConfigOptionFloat support_points_minimal_distance; + ((ConfigOptionInt, support_points_density_relative)) + ((ConfigOptionFloat, support_points_minimal_distance)) // Now for the base pool (pad) ///////////////////////////////////////////// // Enabling or disabling support creation - ConfigOptionBool pad_enable; + ((ConfigOptionBool, pad_enable)) // The thickness of the pad walls - ConfigOptionFloat pad_wall_thickness /*= 2*/; + ((ConfigOptionFloat, pad_wall_thickness))/*= 2*/ // The height of the pad from the bottom to the top not considering the pit - ConfigOptionFloat pad_wall_height /*= 5*/; + ((ConfigOptionFloat, pad_wall_height))/*= 5*/ // How far should the pad extend around the contained geometry - ConfigOptionFloat pad_brim_size; + ((ConfigOptionFloat, pad_brim_size)) // The greatest distance where two individual pads are merged into one. The // distance is measured roughly from the centroids of the pads. - ConfigOptionFloat pad_max_merge_distance /*= 50*/; + ((ConfigOptionFloat, pad_max_merge_distance))/*= 50*/ // The smoothing radius of the pad edges - // ConfigOptionFloat pad_edge_radius /*= 1*/; + // ((ConfigOptionFloat, pad_edge_radius))/*= 1*/; // The slope of the pad wall... - ConfigOptionFloat pad_wall_slope; + ((ConfigOptionFloat, pad_wall_slope)) // ///////////////////////////////////////////////////////////////////////// // Zero elevation mode parameters: @@ -1215,21 +992,21 @@ public: // ///////////////////////////////////////////////////////////////////////// // Disable the elevation (ignore its value) and use the zero elevation mode - ConfigOptionBool pad_around_object; + ((ConfigOptionBool, pad_around_object)) - ConfigOptionBool pad_around_object_everywhere; + ((ConfigOptionBool, pad_around_object_everywhere)) // This is the gap between the object bottom and the generated pad - ConfigOptionFloat pad_object_gap; + ((ConfigOptionFloat, pad_object_gap)) // How far to place the connector sticks on the object pad perimeter - ConfigOptionFloat pad_object_connector_stride; + ((ConfigOptionFloat, pad_object_connector_stride)) // The width of the connectors sticks - ConfigOptionFloat pad_object_connector_width; + ((ConfigOptionFloat, pad_object_connector_width)) // How much should the tiny connectors penetrate into the model body - ConfigOptionFloat pad_object_connector_penetration; + ((ConfigOptionFloat, pad_object_connector_penetration)) // ///////////////////////////////////////////////////////////////////////// // Model hollowing parameters: @@ -1240,169 +1017,85 @@ public: // - resin removal. // ///////////////////////////////////////////////////////////////////////// - ConfigOptionBool hollowing_enable; + ((ConfigOptionBool, hollowing_enable)) // The minimum thickness of the model walls to maintain. Note that the // resulting walls may be thicker due to smoothing out fine cavities where // resin could stuck. - ConfigOptionFloat hollowing_min_thickness; + ((ConfigOptionFloat, hollowing_min_thickness)) // Indirectly controls the voxel size (resolution) used by openvdb - ConfigOptionFloat hollowing_quality; + ((ConfigOptionFloat, hollowing_quality)) // Indirectly controls the minimum size of created cavities. - ConfigOptionFloat hollowing_closing_distance; + ((ConfigOptionFloat, hollowing_closing_distance)) +) -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(layer_height); - OPT_PTR(faded_layers); - OPT_PTR(slice_closing_radius); - OPT_PTR(supports_enable); - OPT_PTR(support_head_front_diameter); - OPT_PTR(support_head_penetration); - OPT_PTR(support_head_width); - OPT_PTR(support_pillar_diameter); - OPT_PTR(support_small_pillar_diameter_percent); - OPT_PTR(support_max_bridges_on_pillar); - OPT_PTR(support_pillar_connection_mode); - OPT_PTR(support_buildplate_only); - OPT_PTR(support_pillar_widening_factor); - OPT_PTR(support_base_diameter); - OPT_PTR(support_base_height); - OPT_PTR(support_base_safety_distance); - OPT_PTR(support_critical_angle); - OPT_PTR(support_max_bridge_length); - OPT_PTR(support_max_pillar_link_distance); - OPT_PTR(support_points_density_relative); - OPT_PTR(support_points_minimal_distance); - OPT_PTR(support_object_elevation); - OPT_PTR(pad_enable); - OPT_PTR(pad_wall_thickness); - OPT_PTR(pad_wall_height); - OPT_PTR(pad_brim_size); - OPT_PTR(pad_max_merge_distance); - // OPT_PTR(pad_edge_radius); - OPT_PTR(pad_wall_slope); - OPT_PTR(pad_around_object); - OPT_PTR(pad_around_object_everywhere); - OPT_PTR(pad_object_gap); - OPT_PTR(pad_object_connector_stride); - OPT_PTR(pad_object_connector_width); - OPT_PTR(pad_object_connector_penetration); - OPT_PTR(hollowing_enable); - OPT_PTR(hollowing_min_thickness); - OPT_PTR(hollowing_quality); - OPT_PTR(hollowing_closing_distance); - } -}; +PRINT_CONFIG_CLASS_DEFINE( + SLAMaterialConfig, -class SLAMaterialConfig : public StaticPrintConfig -{ - STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig) -public: - ConfigOptionFloat initial_layer_height; - ConfigOptionFloat bottle_cost; - ConfigOptionFloat bottle_volume; - ConfigOptionFloat bottle_weight; - ConfigOptionFloat material_density; - ConfigOptionFloat exposure_time; - ConfigOptionFloat initial_exposure_time; - ConfigOptionFloats material_correction; -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(initial_layer_height); - OPT_PTR(bottle_cost); - OPT_PTR(bottle_volume); - OPT_PTR(bottle_weight); - OPT_PTR(material_density); - OPT_PTR(exposure_time); - OPT_PTR(initial_exposure_time); - OPT_PTR(material_correction); - } -}; + ((ConfigOptionFloat, initial_layer_height)) + ((ConfigOptionFloat, bottle_cost)) + ((ConfigOptionFloat, bottle_volume)) + ((ConfigOptionFloat, bottle_weight)) + ((ConfigOptionFloat, material_density)) + ((ConfigOptionFloat, exposure_time)) + ((ConfigOptionFloat, initial_exposure_time)) + ((ConfigOptionFloats, material_correction)) +) -class SLAPrinterConfig : public StaticPrintConfig -{ - STATIC_PRINT_CONFIG_CACHE(SLAPrinterConfig) -public: - ConfigOptionEnum printer_technology; - ConfigOptionPoints bed_shape; - ConfigOptionFloat max_print_height; - ConfigOptionFloat display_width; - ConfigOptionFloat display_height; - ConfigOptionInt display_pixels_x; - ConfigOptionInt display_pixels_y; - ConfigOptionEnum display_orientation; - ConfigOptionBool display_mirror_x; - ConfigOptionBool display_mirror_y; - ConfigOptionFloats relative_correction; - ConfigOptionFloat absolute_correction; - ConfigOptionFloat elefant_foot_compensation; - ConfigOptionFloat elefant_foot_min_width; - ConfigOptionFloat gamma_correction; - ConfigOptionFloat fast_tilt_time; - ConfigOptionFloat slow_tilt_time; - ConfigOptionFloat area_fill; - ConfigOptionFloat min_exposure_time; - ConfigOptionFloat max_exposure_time; - ConfigOptionFloat min_initial_exposure_time; - ConfigOptionFloat max_initial_exposure_time; -protected: - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - OPT_PTR(printer_technology); - OPT_PTR(bed_shape); - OPT_PTR(max_print_height); - OPT_PTR(display_width); - OPT_PTR(display_height); - OPT_PTR(display_pixels_x); - OPT_PTR(display_pixels_y); - OPT_PTR(display_mirror_x); - OPT_PTR(display_mirror_y); - OPT_PTR(display_orientation); - OPT_PTR(relative_correction); - OPT_PTR(absolute_correction); - OPT_PTR(elefant_foot_compensation); - OPT_PTR(elefant_foot_min_width); - OPT_PTR(gamma_correction); - OPT_PTR(fast_tilt_time); - OPT_PTR(slow_tilt_time); - OPT_PTR(area_fill); - OPT_PTR(min_exposure_time); - OPT_PTR(max_exposure_time); - OPT_PTR(min_initial_exposure_time); - OPT_PTR(max_initial_exposure_time); - } -}; +PRINT_CONFIG_CLASS_DEFINE( + SLAPrinterConfig, -class SLAFullPrintConfig : public SLAPrinterConfig, public SLAPrintConfig, public SLAPrintObjectConfig, public SLAMaterialConfig -{ - STATIC_PRINT_CONFIG_CACHE_DERIVED(SLAFullPrintConfig) - SLAFullPrintConfig() : SLAPrinterConfig(0), SLAPrintConfig(0), SLAPrintObjectConfig(0), SLAMaterialConfig(0) { initialize_cache(); *this = s_cache_SLAFullPrintConfig.defaults(); } + ((ConfigOptionEnum, printer_technology)) + ((ConfigOptionPoints, bed_shape)) + ((ConfigOptionFloat, max_print_height)) + ((ConfigOptionFloat, display_width)) + ((ConfigOptionFloat, display_height)) + ((ConfigOptionInt, display_pixels_x)) + ((ConfigOptionInt, display_pixels_y)) + ((ConfigOptionEnum,display_orientation)) + ((ConfigOptionBool, display_mirror_x)) + ((ConfigOptionBool, display_mirror_y)) + ((ConfigOptionFloats, relative_correction)) + ((ConfigOptionFloat, absolute_correction)) + ((ConfigOptionFloat, elefant_foot_compensation)) + ((ConfigOptionFloat, elefant_foot_min_width)) + ((ConfigOptionFloat, gamma_correction)) + ((ConfigOptionFloat, fast_tilt_time)) + ((ConfigOptionFloat, slow_tilt_time)) + ((ConfigOptionFloat, area_fill)) + ((ConfigOptionFloat, min_exposure_time)) + ((ConfigOptionFloat, max_exposure_time)) + ((ConfigOptionFloat, min_initial_exposure_time)) + ((ConfigOptionFloat, max_initial_exposure_time)) +) -public: - // Validate the SLAFullPrintConfig. Returns an empty string on success, otherwise an error message is returned. -// std::string validate(); - -protected: - // Protected constructor to be called to initialize ConfigCache::m_default. - SLAFullPrintConfig(int) : SLAPrinterConfig(0), SLAPrintConfig(0), SLAPrintObjectConfig(0), SLAMaterialConfig(0) {} - void initialize(StaticCacheBase &cache, const char *base_ptr) - { - this->SLAPrinterConfig ::initialize(cache, base_ptr); - this->SLAPrintConfig ::initialize(cache, base_ptr); - this->SLAPrintObjectConfig::initialize(cache, base_ptr); - this->SLAMaterialConfig ::initialize(cache, base_ptr); - } -}; +PRINT_CONFIG_CLASS_DERIVED_DEFINE0( + SLAFullPrintConfig, + (SLAPrinterConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAMaterialConfig) +) #undef STATIC_PRINT_CONFIG_CACHE #undef STATIC_PRINT_CONFIG_CACHE_BASE #undef STATIC_PRINT_CONFIG_CACHE_DERIVED -#undef OPT_PTR +#undef PRINT_CONFIG_CLASS_ELEMENT_DEFINITION +#undef PRINT_CONFIG_CLASS_ELEMENT_EQUAL +#undef PRINT_CONFIG_CLASS_ELEMENT_HASH +#undef PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION +#undef PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2 +#undef PRINT_CONFIG_CLASS_DEFINE +#undef PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST +#undef PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM +#undef PRINT_CONFIG_CLASS_DERIVED_DEFINE +#undef PRINT_CONFIG_CLASS_DERIVED_DEFINE0 +#undef PRINT_CONFIG_CLASS_DERIVED_DEFINE1 +#undef PRINT_CONFIG_CLASS_DERIVED_HASH +#undef PRINT_CONFIG_CLASS_DERIVED_EQUAL +#undef PRINT_CONFIG_CLASS_DERIVED_INITCACHE_ITEM +#undef PRINT_CONFIG_CLASS_DERIVED_INITCACHE +#undef PRINT_CONFIG_CLASS_DERIVED_INITIALIZER +#undef PRINT_CONFIG_CLASS_DERIVED_INITIALIZER_ITEM class CLIActionsConfigDef : public ConfigDef { diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index d8b60bc8465..52a5e7d8396 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -108,7 +108,7 @@ std::string get_extrusion_axis() %code{% if (GCodeConfig* config = dynamic_cast(THIS)) { - RETVAL = config->get_extrusion_axis(); + RETVAL = get_extrusion_axis(*config); } else { CONFESS("This StaticConfig object does not provide get_extrusion_axis()"); } From fa71246ca4ebc4e9eb97f511703e703f7dab9c94 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 26 Apr 2021 19:56:28 +0200 Subject: [PATCH 131/154] Fix normal direction when exporting STL (#6406) The export function does not depend on Model/ModelObject::mesh() family of functions, changing them might break the already too brittle code. --- src/libslic3r/Model.cpp | 12 ------------ src/libslic3r/Model.hpp | 2 -- src/libslic3r/PrintObject.cpp | 1 + src/libslic3r/TriangleMesh.cpp | 19 ++++++++++++------- src/slic3r/GUI/GLCanvas3D.cpp | 1 - src/slic3r/GUI/Plater.cpp | 33 +++++++++++++++++++++++++++++---- 6 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d48443181fd..8b829fc1387 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -833,18 +833,6 @@ indexed_triangle_set ModelObject::raw_indexed_triangle_set() const return out; } -// Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. -TriangleMesh ModelObject::full_raw_mesh() const -{ - TriangleMesh mesh; - for (const ModelVolume *v : this->volumes) - { - TriangleMesh vol_mesh(v->mesh()); - vol_mesh.transform(v->get_matrix()); - mesh.merge(vol_mesh); - } - return mesh; -} const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 868639ee807..50797aeeb56 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -289,8 +289,6 @@ public: TriangleMesh raw_mesh() const; // The same as above, but producing a lightweight indexed_triangle_set. indexed_triangle_set raw_indexed_triangle_set() const; - // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. - TriangleMesh full_raw_mesh() const; // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. // This bounding box is only used for the actual slicing. const BoundingBoxf3& raw_bounding_box() const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index cbf3e71ab73..458ff19ce66 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2207,6 +2207,7 @@ std::vector PrintObject::slice_volumes( TriangleMesh vol_mesh(model_volume.mesh()); vol_mesh.transform(model_volume.get_matrix(), true); mesh.merge(vol_mesh); + mesh.repair(false); } if (mesh.stl.stats.number_of_facets > 0) { mesh.transform(m_trafo, true); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index f8fa1ca17f1..c6d35b2c3a6 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -357,10 +357,14 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) its_transform(its, t); if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. - this->repair(false); - stl_reverse_all_facets(&stl); - this->its.clear(); - this->require_shared_vertices(); + // As for the assert: the repair function would fix the normals, reversing would + // break them again. The caller should provide a mesh that does not need repair. + // The repair call is left here so things don't break more than they were. + assert(this->repaired); + this->repair(false); + stl_reverse_all_facets(&stl); + this->its.clear(); + this->require_shared_vertices(); } } @@ -369,11 +373,12 @@ void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) stl_transform(&stl, m); its_transform(its, m); if (fix_left_handed && m.determinant() < 0.) { - // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. + // See comments in function above. + assert(this->repaired); this->repair(false); stl_reverse_all_facets(&stl); - this->its.clear(); - this->require_shared_vertices(); + this->its.clear(); + this->require_shared_vertices(); } } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 97038723bbf..c68b98072bf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,7 +1,6 @@ #include "libslic3r/libslic3r.h" #include "GLCanvas3D.hpp" -#include "admesh/stl.h" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5db983f43a9..e94348fb226 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5085,6 +5085,30 @@ void Plater::export_stl(bool extended, bool selection_only) if (selection_only && (obj_idx == -1 || selection.is_wipe_tower())) return; + // Following lambda generates a combined mesh for export with normals pointing outwards. + auto mesh_to_export = [](const ModelObject* mo, bool instances) -> TriangleMesh { + TriangleMesh mesh; + for (const ModelVolume *v : mo->volumes) + if (v->is_model_part()) { + TriangleMesh vol_mesh(v->mesh()); + vol_mesh.repair(); + vol_mesh.transform(v->get_matrix(), true); + mesh.merge(vol_mesh); + } + mesh.repair(); + if (instances) { + TriangleMesh vols_mesh(mesh); + mesh = TriangleMesh(); + for (const ModelInstance *i : mo->instances) { + TriangleMesh m = vols_mesh; + m.transform(i->get_matrix(), true); + mesh.merge(m); + } + } + mesh.repair(); + return mesh; + }; + TriangleMesh mesh; if (p->printer_technology == ptFFF) { if (selection_only) { @@ -5092,20 +5116,21 @@ void Plater::export_stl(bool extended, bool selection_only) if (selection.get_mode() == Selection::Instance) { if (selection.is_single_full_object()) - mesh = model_object->mesh(); + mesh = mesh_to_export(model_object, true); else - mesh = model_object->full_raw_mesh(); + mesh = mesh_to_export(model_object, false); } else { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); mesh = model_object->volumes[volume->volume_idx()]->mesh(); - mesh.transform(volume->get_volume_transformation().get_matrix()); + mesh.transform(volume->get_volume_transformation().get_matrix(), true); mesh.translate(-model_object->origin_translation.cast()); } } else { - mesh = p->model.mesh(); + for (const ModelObject *o : p->model.objects) + mesh.merge(mesh_to_export(o, true)); } } else From 22af3b8e3041ef726978cfcbde7db1499f9a153b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 26 Apr 2021 20:44:53 +0200 Subject: [PATCH 132/154] Fixed a memory leak when repairing an external stl --- src/slic3r/GUI/MainFrame.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 9c426dcd965..666e3327b14 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1483,10 +1483,10 @@ void MainFrame::repair_stl() output_file = dlg.GetPath(); } - auto tmesh = new Slic3r::TriangleMesh(); - tmesh->ReadSTLFile(input_file.ToUTF8().data()); - tmesh->repair(); - tmesh->WriteOBJFile(output_file.ToUTF8().data()); + Slic3r::TriangleMesh tmesh; + tmesh.ReadSTLFile(input_file.ToUTF8().data()); + tmesh.repair(); + tmesh.WriteOBJFile(output_file.ToUTF8().data()); Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair")); } From 343eb6006b8c5ddb6fcf57017b06aaa6b048a042 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 27 Apr 2021 09:45:15 +0200 Subject: [PATCH 133/154] Tech ENABLE_SEAMS_VISUALIZATION -> 1st installment of seams visualization in preview --- src/libslic3r/GCode/GCodeProcessor.cpp | 40 +++++++++++++++++++++++++ src/libslic3r/GCode/GCodeProcessor.hpp | 41 +++++++++++++++++++++++--- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/GCodeViewer.cpp | 40 +++++++++++++++++++++++++ src/slic3r/GUI/GCodeViewer.hpp | 3 ++ src/slic3r/GUI/GUI_Preview.cpp | 6 ++++ src/slic3r/GUI/GUI_Preview.hpp | 3 ++ 7 files changed, 131 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7a179097144..f4ee8964dba 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1060,6 +1060,9 @@ void GCodeProcessor::reset() #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER m_line_id = 0; +#if ENABLE_SEAMS_VISUALIZATION + m_last_line_id = 0; +#endif // ENABLE_SEAMS_VISUALIZATION #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER m_feedrate = 0.0f; m_width = 0.0f; @@ -1443,6 +1446,10 @@ void GCodeProcessor::process_tags(const std::string_view comment) // extrusion role tag if (boost::starts_with(comment, reserved_tag(ETags::Role))) { m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length())); +#if ENABLE_SEAMS_VISUALIZATION + if (m_extrusion_role == erExternalPerimeter) + m_seams_detector.activate(true); +#endif // ENABLE_SEAMS_VISUALIZATION return; } @@ -2354,6 +2361,29 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) machine.calculate_time(TimeProcessor::Planner::queue_size); } +#if ENABLE_SEAMS_VISUALIZATION + // check for seam starting vertex + if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && m_seams_detector.is_active() && !m_seams_detector.has_first_vertex()) + m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); + // check for seam ending vertex and store the resulting move + else if ((type != EMoveType::Extrude || m_extrusion_role != erExternalPerimeter) && m_seams_detector.is_active()) { + auto set_end_position = [this](const Vec3f& pos) { + m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); + }; + + assert(m_seams_detector.has_first_vertex()); + const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); + const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; + const std::optional first_vertex = m_seams_detector.get_first_vertex(); + const Vec3f mid_pos = 0.5f * (new_pos + first_vertex.value()); + set_end_position(mid_pos); + store_move_vertex(EMoveType::Seam); + set_end_position(curr_pos); + + m_seams_detector.activate(false); + } +#endif // ENABLE_SEAMS_VISUALIZATION + // store move store_move_vertex(type); } @@ -2807,9 +2837,19 @@ void GCodeProcessor::process_T(const std::string_view command) void GCodeProcessor::store_move_vertex(EMoveType type) { +#if ENABLE_SEAMS_VISUALIZATION + m_last_line_id = (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ? + m_line_id + 1 : + ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); +#endif // ENABLE_SEAMS_VISUALIZATION + MoveVertex vertex = { #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER +#if ENABLE_SEAMS_VISUALIZATION + m_last_line_id, +#else (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ? m_line_id + 1 : m_line_id, +#endif // ENABLE_SEAMS_VISUALIZATION #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER type, m_extrusion_role, diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index cf55bf86e79..60aad8a6f57 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -12,6 +12,9 @@ #include #include #include +#if ENABLE_SEAMS_VISUALIZATION +#include +#endif // ENABLE_SEAMS_VISUALIZATION namespace Slic3r { @@ -20,6 +23,9 @@ namespace Slic3r { Noop, Retract, Unretract, +#if ENABLE_SEAMS_VISUALIZATION + Seam, +#endif // ENABLE_SEAMS_VISUALIZATION Tool_change, Color_change, Pause_Print, @@ -370,8 +376,7 @@ namespace Slic3r { #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; - void reset() - { + void reset() { time = 0; moves = std::vector(); bed_shape = Pointfs(); @@ -380,8 +385,7 @@ namespace Slic3r { settings_ids.reset(); } #else - void reset() - { + void reset() { moves = std::vector(); bed_shape = Pointfs(); extruder_colors = std::vector(); @@ -391,6 +395,29 @@ namespace Slic3r { #endif // ENABLE_GCODE_VIEWER_STATISTICS }; +#if ENABLE_SEAMS_VISUALIZATION + class SeamsDetector + { + bool m_active{ false }; + std::optional m_first_vertex; + + public: + void activate(bool active) { + if (m_active != active) { + m_active = active; + if (m_active) + m_first_vertex.reset(); + } + } + + std::optional get_first_vertex() const { return m_first_vertex; } + void set_first_vertex(const Vec3f& vertex) { m_first_vertex = vertex; } + + bool is_active() const { return m_active; } + bool has_first_vertex() const { return m_first_vertex.has_value(); } + }; +#endif // ENABLE_SEAMS_VISUALIZATION + #if ENABLE_GCODE_VIEWER_DATA_CHECKING struct DataChecker { @@ -476,6 +503,9 @@ namespace Slic3r { #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER unsigned int m_line_id; +#if ENABLE_SEAMS_VISUALIZATION + unsigned int m_last_line_id; +#endif // ENABLE_SEAMS_VISUALIZATION #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER float m_feedrate; // mm/s float m_width; // mm @@ -494,6 +524,9 @@ namespace Slic3r { unsigned int m_layer_id; CpColor m_cp_color; bool m_use_volumetric_e; +#if ENABLE_SEAMS_VISUALIZATION + SeamsDetector m_seams_detector; +#endif // ENABLE_SEAMS_VISUALIZATION enum class EProducer { diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 303ffe92741..1a62f53b91a 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,8 @@ #define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) // Enable a modified version of automatic downscale on load of objects too big #define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) +// Enable visualization of seams in preview +#define ENABLE_SEAMS_VISUALIZATION (1 && ENABLE_2_4_0_ALPHA0) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index eacf69c917b..47c40f50273 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -143,6 +143,9 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const case EMoveType::Custom_GCode: case EMoveType::Retract: case EMoveType::Unretract: +#if ENABLE_SEAMS_VISUALIZATION + case EMoveType::Seam: +#endif // ENABLE_SEAMS_VISUALIZATION case EMoveType::Extrude: { // use rounding to reduce the number of generated paths #if ENABLE_SPLITTED_VERTEX_BUFFER @@ -540,6 +543,9 @@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ const std::vector GCodeViewer::Options_Colors {{ { 0.803f, 0.135f, 0.839f }, // Retractions { 0.287f, 0.679f, 0.810f }, // Unretractions +#if ENABLE_SEAMS_VISUALIZATION + { 0.900f, 0.900f, 0.900f }, // Seams +#endif // ENABLE_SEAMS_VISUALIZATION { 0.758f, 0.744f, 0.389f }, // ToolChanges { 0.856f, 0.582f, 0.546f }, // ColorChanges { 0.322f, 0.942f, 0.512f }, // PausePrints @@ -582,11 +588,20 @@ GCodeViewer::GCodeViewer() case EMoveType::Pause_Print: case EMoveType::Custom_GCode: case EMoveType::Retract: +#if ENABLE_SEAMS_VISUALIZATION + case EMoveType::Unretract: + case EMoveType::Seam: { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; + buffer.vertices.format = VBuffer::EFormat::Position; + break; + } +#else case EMoveType::Unretract: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; buffer.vertices.format = VBuffer::EFormat::Position; break; } +#endif // ENABLE_SEAMS_VISUALIZATION case EMoveType::Wipe: case EMoveType::Extrude: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; @@ -796,10 +811,18 @@ void GCodeViewer::render() const case EMoveType::Pause_Print: case EMoveType::Custom_GCode: case EMoveType::Retract: +#if ENABLE_SEAMS_VISUALIZATION + case EMoveType::Unretract: + case EMoveType::Seam: { + buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; + break; + } +#else case EMoveType::Unretract: { buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } +#endif // ENABLE_SEAMS_VISUALIZATION case EMoveType::Wipe: case EMoveType::Extrude: { buffer.shader = "gouraud_light"; @@ -938,6 +961,9 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, static_cast(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe)); flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract)); flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract)); +#if ENABLE_SEAMS_VISUALIZATION + flags = set_flag(flags, static_cast(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam)); +#endif // ENABLE_SEAMS_VISUALIZATION flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change)); flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change)); flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print)); @@ -958,6 +984,9 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast(Preview::OptionType::Wipe))); set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); +#if ENABLE_SEAMS_VISUALIZATION + set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast(Preview::OptionType::Seams))); +#endif // ENABLE_SEAMS_VISUALIZATION set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); @@ -3163,6 +3192,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EMoveType::Custom_GCode: { color = Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; break; } case EMoveType::Retract: { color = Options_Colors[static_cast(EOptionsColors::Retractions)]; break; } case EMoveType::Unretract: { color = Options_Colors[static_cast(EOptionsColors::Unretractions)]; break; } +#if ENABLE_SEAMS_VISUALIZATION + case EMoveType::Seam: { color = Options_Colors[static_cast(EOptionsColors::Seams)]; break; } +#endif // ENABLE_SEAMS_VISUALIZATION case EMoveType::Extrude: { if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || @@ -4557,7 +4589,12 @@ void GCodeViewer::render_legend() const available(EMoveType::Pause_Print) || available(EMoveType::Retract) || available(EMoveType::Tool_change) || +#if ENABLE_SEAMS_VISUALIZATION + available(EMoveType::Unretract) || + available(EMoveType::Seam); +#else available(EMoveType::Unretract); +#endif // ENABLE_SEAMS_VISUALIZATION }; auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { @@ -4575,6 +4612,9 @@ void GCodeViewer::render_legend() const // items add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions")); +#if ENABLE_SEAMS_VISUALIZATION + add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams")); +#endif // ENABLE_SEAMS_VISUALIZATION add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 051260b72c1..2ccda6f5dea 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -46,6 +46,9 @@ class GCodeViewer { Retractions, Unretractions, +#if ENABLE_SEAMS_VISUALIZATION + Seams, +#endif // ENABLE_SEAMS_VISUALIZATION ToolChanges, ColorChanges, PausePrints, diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index e67ddb04526..676d8455856 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -250,6 +250,9 @@ bool Preview::init(wxWindow* parent, Model* model) get_option_type_string(OptionType::Wipe) + "|0|" + get_option_type_string(OptionType::Retractions) + "|0|" + get_option_type_string(OptionType::Unretractions) + "|0|" + +#if ENABLE_SEAMS_VISUALIZATION + get_option_type_string(OptionType::Seams) + "|0|" + +#endif // ENABLE_SEAMS_VISUALIZATION get_option_type_string(OptionType::ToolChanges) + "|0|" + get_option_type_string(OptionType::ColorChanges) + "|0|" + get_option_type_string(OptionType::PausePrints) + "|0|" + @@ -1008,6 +1011,9 @@ wxString Preview::get_option_type_string(OptionType type) const case OptionType::Wipe: { return _L("Wipe"); } case OptionType::Retractions: { return _L("Retractions"); } case OptionType::Unretractions: { return _L("Deretractions"); } +#if ENABLE_SEAMS_VISUALIZATION + case OptionType::Seams: { return _L("Seams"); } +#endif // ENABLE_SEAMS_VISUALIZATION case OptionType::ToolChanges: { return _L("Tool changes"); } case OptionType::ColorChanges: { return _L("Color changes"); } case OptionType::PausePrints: { return _L("Print pauses"); } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 3bf0e21ae3c..d49e6e7ac11 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -116,6 +116,9 @@ public: Wipe, Retractions, Unretractions, +#if ENABLE_SEAMS_VISUALIZATION + Seams, +#endif // ENABLE_SEAMS_VISUALIZATION ToolChanges, ColorChanges, PausePrints, From d078aedfbe1b64ce463c4931c38dc3dead2a7191 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 27 Apr 2021 10:46:42 +0200 Subject: [PATCH 134/154] Replace label Skirt with Skirt/Brim in preview legend --- src/libslic3r/ExtrusionEntity.cpp | 4 ++-- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 3284bc39e4a..721c86ed3bc 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -318,7 +318,7 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role) case erIroning : return L("Ironing"); case erBridgeInfill : return L("Bridge infill"); case erGapFill : return L("Gap fill"); - case erSkirt : return L("Skirt"); + case erSkirt : return L("Skirt/Brim"); case erSupportMaterial : return L("Support material"); case erSupportMaterialInterface : return L("Support material interface"); case erWipeTower : return L("Wipe tower"); @@ -349,7 +349,7 @@ ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role) return erBridgeInfill; else if (role == L("Gap fill")) return erGapFill; - else if (role == L("Skirt")) + else if (role == L("Skirt/Brim")) return erSkirt; else if (role == L("Support material")) return erSupportMaterial; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index e67ddb04526..d9b6adea466 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -235,7 +235,7 @@ bool Preview::init(wxWindow* parent, Model* model) _L("Ironing") + "|1|" + _L("Bridge infill") + "|1|" + _L("Gap fill") + "|1|" + - _L("Skirt") + "|1|" + + _L("Skirt/Brim") + "|1|" + _L("Support material") + "|1|" + _L("Support material interface") + "|1|" + _L("Wipe tower") + "|1|" + From 9516470b2f64623d2b8510ad40385e1cc53d9898 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 27 Apr 2021 11:11:21 +0200 Subject: [PATCH 135/154] Tech ENABLE_SEAMS_VISUALIZATION -> Fixed build on Mac --- src/libslic3r/GCode/GCodeProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index f4ee8964dba..c64725e508d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2375,7 +2375,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; const std::optional first_vertex = m_seams_detector.get_first_vertex(); - const Vec3f mid_pos = 0.5f * (new_pos + first_vertex.value()); + const Vec3f mid_pos = 0.5f * (new_pos + *first_vertex); set_end_position(mid_pos); store_move_vertex(EMoveType::Seam); set_end_position(curr_pos); From 3fe61c288787bc549edbf857b26def06b8b5e17e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 27 Apr 2021 15:12:45 +0200 Subject: [PATCH 136/154] Tech ENABLE_SEAMS_VISUALIZATION -> Added threshold to place seams --- src/libslic3r/GCode/GCodeProcessor.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index c64725e508d..a4fd429b5d3 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2375,10 +2375,12 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; const std::optional first_vertex = m_seams_detector.get_first_vertex(); - const Vec3f mid_pos = 0.5f * (new_pos + *first_vertex); - set_end_position(mid_pos); - store_move_vertex(EMoveType::Seam); - set_end_position(curr_pos); + // the threshold value = 0.25 is arbitrary, we may find some smarter condition later + if ((new_pos - *first_vertex).norm() < 0.25f) { + set_end_position(0.5f * (new_pos + *first_vertex)); + store_move_vertex(EMoveType::Seam); + set_end_position(curr_pos); + } m_seams_detector.activate(false); } From bd70aebfeef490cae34e33e47e20c0f4ff392fcc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 28 Apr 2021 13:58:16 +0200 Subject: [PATCH 137/154] Changed order of rendering of sidebar hints to avoid artifacts due to depth buffer cleanup made by gizmo renderers --- src/slic3r/GUI/GLCanvas3D.cpp | 4 +++- src/slic3r/GUI/Selection.cpp | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c68b98072bf..da8c84605ee 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1681,8 +1681,10 @@ void GLCanvas3D::render() if (m_picking_enabled) m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); - _render_current_gizmo(); + // sidebar hints need to be rendered before the gizmos because the depth buffer + // could be invalidated by the following gizmo render methods _render_selection_sidebar_hints(); + _render_current_gizmo(); #if ENABLE_RENDER_PICKING_PASS } #endif // ENABLE_RENDER_PICKING_PASS diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2acb8cb85b1..fd687374937 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1217,7 +1217,7 @@ void Selection::render_center(bool gizmo_is_dragging) const if (!m_valid || is_empty() || m_quadric == nullptr) return; - Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); + const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); glsafe(::glDisable(GL_DEPTH_TEST)); @@ -1286,7 +1286,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const } else { glsafe(::glTranslated(center(0), center(1), center(2))); if (requires_local_axes()) { - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } } @@ -1976,7 +1976,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co if (pos == std::string::npos) return; - double min_z = std::stod(field.substr(pos + 1)); + const double min_z = std::stod(field.substr(pos + 1)); // extract type field = field.substr(0, pos); @@ -1984,7 +1984,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co if (pos == std::string::npos) return; - int type = std::stoi(field.substr(pos + 1)); + const int type = std::stoi(field.substr(pos + 1)); const BoundingBoxf3& box = get_bounding_box(); @@ -1995,8 +1995,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co // view dependend order of rendering to keep correct transparency bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward(); - float z1 = camera_on_top ? min_z : max_z; - float z2 = camera_on_top ? max_z : min_z; + const float z1 = camera_on_top ? min_z : max_z; + const float z2 = camera_on_top ? max_z : min_z; glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); @@ -2004,7 +2004,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); ::glBegin(GL_QUADS); - if ((camera_on_top && (type == 1)) || (!camera_on_top && (type == 2))) + if ((camera_on_top && type == 1) || (!camera_on_top && type == 2)) ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); else ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); @@ -2015,7 +2015,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co glsafe(::glEnd()); ::glBegin(GL_QUADS); - if ((camera_on_top && (type == 2)) || (!camera_on_top && (type == 1))) + if ((camera_on_top && type == 2) || (!camera_on_top && type == 1)) ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); else ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); From 681737a3d64ca87805f364f205164114e8de00ff Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 28 Apr 2021 16:06:49 +0200 Subject: [PATCH 138/154] Follow-up of 2c6472ebc33902ade0d528634209e70a3aa08f99 -> Ensure backward compatibility --- src/libslic3r/ExtrusionEntity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 721c86ed3bc..10e85984826 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -349,7 +349,7 @@ ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role) return erBridgeInfill; else if (role == L("Gap fill")) return erGapFill; - else if (role == L("Skirt/Brim")) + else if (role == L("Skirt") || role == L("Skirt/Brim")) // "Skirt" is for backward compatibility with 2.3.1 and earlier return erSkirt; else if (role == L("Support material")) return erSupportMaterial; From 0f36a792a7372c46409862f007a1f091b10a9a7b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 29 Apr 2021 09:09:49 +0200 Subject: [PATCH 139/154] Fixed missing ending cap for toolpaths having a single segment --- src/slic3r/GUI/GCodeViewer.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index eacf69c917b..38aa56e8b9d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1595,13 +1595,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS - + bool is_first_segment = (last_path.vertices_count() == 1); + if (is_first_segment || vbuffer_size == 0) { +#else if (last_path.vertices_count() == 1 || vbuffer_size == 0) { +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // 1st segment or restart into a new vertex buffer // =============================================== #if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS - if (last_path.vertices_count() == 1) + if (is_first_segment) // starting cap triangles append_starting_cap_triangles(indices, first_seg_v_offsets); #endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS @@ -1679,7 +1681,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) // ending cap triangles - append_ending_cap_triangles(indices, non_first_seg_v_offsets); + append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); #endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; From c901b60aa4eaa575b4f0c320048cf3c6f93f3758 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 29 Apr 2021 11:05:11 +0200 Subject: [PATCH 140/154] Layer::make_perimeters() - when merging regions, use OffsetEx instead of safety offset of UnionEx, which may not be robust. --- src/libslic3r/Layer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index b974ff21729..e86a67b6fb2 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -185,7 +185,7 @@ void Layer::make_perimeters() } // merge the surfaces assigned to each group for (std::pair &surfaces_with_extra_perimeters : slices) - new_slices.append(union_ex(surfaces_with_extra_perimeters.second, true), surfaces_with_extra_perimeters.second.front()); + new_slices.append(offset_ex(to_expolygons(surfaces_with_extra_perimeters.second), 10.f), surfaces_with_extra_perimeters.second.front()); } // make perimeters From 96a7a527153847b332c43f437c2576c598860b1a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 30 Apr 2021 11:49:57 +0200 Subject: [PATCH 141/154] Clipper optimization: 1) Removed the already commented-out scaling / unscaling when doing "safe offsetting" 2) Removed some of the "safe offsetting" at calls where it never was used. 3) Reworked Clipper & ClipperUtils to pass Polygons / ExPolygons / Surfaces as input parameters without conversion to ClipperLib::Paths. This should save a lot of memory allocation and copying. 4) Reworked conversions from ClipperLib::Paths & PolyTree to Polygons / ExPolygons to use the move operator to avoid many unnecessary allocations. 5) Reworked some "union with safe ofsetting" to "offset_ex", which should be cheaper. --- src/clipper/clipper.cpp | 71 +-- src/clipper/clipper.hpp | 72 ++- src/libslic3r/Brim.cpp | 10 +- src/libslic3r/ClipperUtils.cpp | 698 +++++++++----------------- src/libslic3r/ClipperUtils.hpp | 404 +++++++++------ src/libslic3r/ExPolygon.hpp | 4 +- src/libslic3r/Fill/FillConcentric.cpp | 2 +- src/libslic3r/Layer.cpp | 2 +- src/libslic3r/Polygon.cpp | 2 +- src/libslic3r/Polygon.hpp | 18 + src/libslic3r/Polyline.hpp | 18 + src/libslic3r/SLA/ConcaveHull.cpp | 17 +- src/libslic3r/SLA/Pad.cpp | 14 +- src/libslic3r/TriangleMesh.cpp | 4 +- src/slic3r/GUI/3DBed.cpp | 2 +- 15 files changed, 616 insertions(+), 722 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 06c91bf3a84..0285d9167f3 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -759,48 +759,6 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) return result; } -bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) -{ - CLIPPERLIB_PROFILE_FUNC(); - std::vector num_edges(ppg.size(), 0); - int num_edges_total = 0; - for (size_t i = 0; i < ppg.size(); ++ i) { - const Path &pg = ppg[i]; - // Remove duplicate end point from a closed input path. - // Remove duplicate points from the end of the input path. - int highI = (int)pg.size() -1; - if (Closed) - while (highI > 0 && (pg[highI] == pg[0])) - --highI; - while (highI > 0 && (pg[highI] == pg[highI -1])) - --highI; - if ((Closed && highI < 2) || (!Closed && highI < 1)) - highI = -1; - num_edges[i] = highI + 1; - num_edges_total += highI + 1; - } - if (num_edges_total == 0) - return false; - - // Allocate a new edge array. - std::vector edges(num_edges_total); - // Fill in the edge array. - bool result = false; - TEdge *p_edge = edges.data(); - for (Paths::size_type i = 0; i < ppg.size(); ++i) - if (num_edges[i]) { - bool res = AddPathInternal(ppg[i], num_edges[i] - 1, PolyTyp, Closed, p_edge); - if (res) { - p_edge += num_edges[i]; - result = true; - } - } - if (result) - // At least some edges were generated. Remember the edge array. - m_edges.emplace_back(std::move(edges)); - return result; -} - bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, bool Closed, TEdge* edges) { CLIPPERLIB_PROFILE_FUNC(); @@ -1103,7 +1061,7 @@ bool Clipper::Execute(ClipType clipType, Paths &solution, CLIPPERLIB_PROFILE_FUNC(); if (m_HasOpenPaths) throw clipperException("Error: PolyTree struct is needed for open path clipping."); - solution.resize(0); + solution.clear(); m_SubjFillType = subjFillType; m_ClipFillType = clipFillType; m_ClipType = clipType; @@ -3426,13 +3384,6 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType } //------------------------------------------------------------------------------ -void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) -{ - for (const Path &path : paths) - AddPath(path, joinType, endType); -} -//------------------------------------------------------------------------------ - void ClipperOffset::FixOrientations() { //fixup orientations of all closed paths if the orientation of the @@ -3875,28 +3826,16 @@ void ReversePaths(Paths& p) } //------------------------------------------------------------------------------ -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) +Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType) { Clipper c; c.StrictlySimple(true); c.AddPath(in_poly, ptSubject, true); - c.Execute(ctUnion, out_polys, fillType, fillType); + Paths out; + c.Execute(ctUnion, out, fillType, fillType); + return out; } -//------------------------------------------------------------------------------ -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) -{ - Clipper c; - c.StrictlySimple(true); - c.AddPaths(in_polys, ptSubject, true); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(Paths &polys, PolyFillType fillType) -{ - SimplifyPolygons(polys, polys, fillType); -} //------------------------------------------------------------------------------ inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index c32bcf87b2c..36b9beee5bb 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -191,9 +191,16 @@ double Area(const Path &poly); inline bool Orientation(const Path &poly) { return Area(poly) >= 0; } int PointInPolygon(const IntPoint &pt, const Path &path); -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); +Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftEvenOdd); +template +inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftEvenOdd) { + Clipper c; + c.StrictlySimple(true); + c.AddPaths(std::forward(in_polys), ptSubject, true); + Paths out; + c.Execute(ctUnion, out, fillType, fillType); + return out; +} void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); void CleanPolygon(Path& poly, double distance = 1.415); @@ -300,7 +307,58 @@ public: m_HasOpenPaths(false) {} ~ClipperBase() { Clear(); } bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); - bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + + template + bool AddPaths(PathsProvider &&paths_provider, PolyType PolyTyp, bool Closed) + { + size_t num_paths = paths_provider.size(); + if (num_paths == 0) + return false; + if (num_paths == 1) + return AddPath(*paths_provider.begin(), PolyTyp, Closed); + + std::vector num_edges(num_paths, 0); + int num_edges_total = 0; + size_t i = 0; + for (const Path &pg : paths_provider) { + // Remove duplicate end point from a closed input path. + // Remove duplicate points from the end of the input path. + int highI = (int)pg.size() -1; + if (Closed) + while (highI > 0 && (pg[highI] == pg[0])) + --highI; + while (highI > 0 && (pg[highI] == pg[highI -1])) + --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) + highI = -1; + num_edges[i ++] = highI + 1; + num_edges_total += highI + 1; + } + if (num_edges_total == 0) + return false; + + // Allocate a new edge array. + std::vector edges(num_edges_total); + // Fill in the edge array. + bool result = false; + TEdge *p_edge = edges.data(); + i = 0; + for (const Path &pg : paths_provider) { + if (num_edges[i]) { + bool res = AddPathInternal(pg, num_edges[i] - 1, PolyTyp, Closed, p_edge); + if (res) { + p_edge += num_edges[i]; + result = true; + } + } + ++ i; + } + if (result) + // At least some edges were generated. Remember the edge array. + m_edges.emplace_back(std::move(edges)); + return result; + } + void Clear(); IntRect GetBounds(); // By default, when three or more vertices are collinear in input polygons (subject or clip), the Clipper object removes the 'inner' vertices before clipping. @@ -461,7 +519,11 @@ public: MiterLimit(miterLimit), ArcTolerance(roundPrecision), ShortestEdgeLength(shortestEdgeLength), m_lowest(-1, 0) {} ~ClipperOffset() { Clear(); } void AddPath(const Path& path, JoinType joinType, EndType endType); - void AddPaths(const Paths& paths, JoinType joinType, EndType endType); + template + void AddPaths(PathsProvider &&paths, JoinType joinType, EndType endType) { + for (const Path &path : paths) + AddPath(path, joinType, endType); + } void Execute(Paths& solution, double delta); void Execute(PolyTree& solution, double delta); void Clear(); diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 19f5ae82e45..16b81e48829 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -139,7 +139,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print, const ConstPrint Polygons no_brim_area_object; for (const ExPolygon &ex_poly : object->layers().front()->lslices) { if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) && is_top_outer_brim) - append(brim_area_object, diff_ex(offset_ex(ex_poly.contour, brim_width + brim_offset), offset_ex(ex_poly.contour, brim_offset))); + append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset))); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) append(no_brim_area_object, offset(ex_poly.holes, -no_brim_offset)); @@ -183,14 +183,14 @@ static ExPolygons inner_brim_area(const Print &print, const ConstPrintObjectPtrs if (top_outer_brim) no_brim_area_object.emplace_back(ex_poly); else - append(brim_area_object, diff_ex(offset_ex(ex_poly.contour, brim_width + brim_offset), offset_ex(ex_poly.contour, brim_offset))); + append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset))); } if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset))); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.contour, no_brim_offset)); + append(no_brim_area_object, to_expolygons(offset(ex_poly.contour, no_brim_offset))); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset)); @@ -317,7 +317,7 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_ islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), jtSquare); } - loops = union_pt_chained_outside_in(loops, false); + loops = union_pt_chained_outside_in(loops); std::reverse(loops.begin(), loops.end()); extrusion_entities_append_loops(brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); @@ -342,7 +342,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance poly.douglas_peucker(SCALED_RESOLUTION); polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); } - loops = union_pt_chained_outside_in(loops, false); + loops = union_pt_chained_outside_in(loops); std::vector loops_pl_by_levels; { diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 477dbf6f180..1cd4a7c2ffd 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -24,7 +24,6 @@ namespace Slic3r { #ifdef CLIPPER_UTILS_DEBUG -bool clipper_export_enabled = false; // For debugging the Clipper library, for providing bug reports to the Clipper author. bool export_clipper_input_polygons_bin(const char *path, const ClipperLib::Paths &input_subject, const ClipperLib::Paths &input_clip) { @@ -57,207 +56,137 @@ err: } #endif /* CLIPPER_UTILS_DEBUG */ -#ifdef CLIPPERUTILS_OFFSET_SCALE -void scaleClipperPolygon(ClipperLib::Path &polygon) -{ - CLIPPERUTILS_PROFILE_FUNC(); - for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { - pit->X <<= CLIPPER_OFFSET_POWER_OF_2; - pit->Y <<= CLIPPER_OFFSET_POWER_OF_2; - } +namespace ClipperUtils { + Points SinglePathProvider::s_end; } -void scaleClipperPolygons(ClipperLib::Paths &polygons) +static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree) { - CLIPPERUTILS_PROFILE_FUNC(); - for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) - for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { - pit->X <<= CLIPPER_OFFSET_POWER_OF_2; - pit->Y <<= CLIPPER_OFFSET_POWER_OF_2; + struct Inner { + static void PolyTreeToExPolygonsRecursive(ClipperLib::PolyNode &polynode, ExPolygons *expolygons) + { + size_t cnt = expolygons->size(); + expolygons->resize(cnt + 1); + (*expolygons)[cnt].contour.points = std::move(polynode.Contour); + (*expolygons)[cnt].holes.resize(polynode.ChildCount()); + for (int i = 0; i < polynode.ChildCount(); ++ i) { + (*expolygons)[cnt].holes[i].points = std::move(polynode.Childs[i]->Contour); + // Add outer polygons contained by (nested within) holes. + for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++ j) + PolyTreeToExPolygonsRecursive(*polynode.Childs[i]->Childs[j], expolygons); + } } -} -void unscaleClipperPolygon(ClipperLib::Path &polygon) -{ - CLIPPERUTILS_PROFILE_FUNC(); - for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { - pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; - pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; - pit->X >>= CLIPPER_OFFSET_POWER_OF_2; - pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; - } -} - -void unscaleClipperPolygons(ClipperLib::Paths &polygons) -{ - CLIPPERUTILS_PROFILE_FUNC(); - for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) - for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { - pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; - pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; - pit->X >>= CLIPPER_OFFSET_POWER_OF_2; - pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; + static size_t PolyTreeCountExPolygons(const ClipperLib::PolyNode &polynode) + { + size_t cnt = 1; + for (int i = 0; i < polynode.ChildCount(); ++ i) { + for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++ j) + cnt += PolyTreeCountExPolygons(*polynode.Childs[i]->Childs[j]); + } + return cnt; } -} -#endif // CLIPPERUTILS_OFFSET_SCALE + }; -//----------------------------------------------------------- -// legacy code from Clipper documentation -void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* expolygons) -{ - size_t cnt = expolygons->size(); - expolygons->resize(cnt + 1); - (*expolygons)[cnt].contour = ClipperPath_to_Slic3rPolygon(polynode.Contour); - (*expolygons)[cnt].holes.resize(polynode.ChildCount()); - for (int i = 0; i < polynode.ChildCount(); ++i) - { - (*expolygons)[cnt].holes[i] = ClipperPath_to_Slic3rPolygon(polynode.Childs[i]->Contour); - //Add outer polygons contained by (nested within) holes ... - for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j) - AddOuterPolyNodeToExPolygons(*polynode.Childs[i]->Childs[j], expolygons); - } -} - -ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree) -{ ExPolygons retval; - for (int i = 0; i < polytree.ChildCount(); ++i) - AddOuterPolyNodeToExPolygons(*polytree.Childs[i], &retval); - return retval; -} -//----------------------------------------------------------- - -Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input) -{ - Polygon retval; - for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) - retval.points.emplace_back(pit->x(), pit->y()); + size_t cnt = 0; + for (int i = 0; i < polytree.ChildCount(); ++ i) + cnt += Inner::PolyTreeCountExPolygons(*polytree.Childs[i]); + retval.reserve(cnt); + for (int i = 0; i < polytree.ChildCount(); ++ i) + Inner::PolyTreeToExPolygonsRecursive(std::move(*polytree.Childs[i]), &retval); return retval; } -Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input) +Polylines PolyTreeToPolylines(ClipperLib::PolyTree &&polytree) { - Polyline retval; - for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) - retval.points.emplace_back(pit->x(), pit->y()); - return retval; -} + struct Inner { + static void AddPolyNodeToPaths(ClipperLib::PolyNode &polynode, Polylines &out) + { + if (! polynode.Contour.empty()) + out.emplace_back(std::move(polynode.Contour)); + for (ClipperLib::PolyNode *child : polynode.Childs) + AddPolyNodeToPaths(*child, out); + } + }; -Slic3r::Polygons ClipperPaths_to_Slic3rPolygons(const ClipperLib::Paths &input) -{ - Slic3r::Polygons retval; - retval.reserve(input.size()); - for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) - retval.emplace_back(ClipperPath_to_Slic3rPolygon(*it)); - return retval; -} - -Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input) -{ - Slic3r::Polylines retval; - retval.reserve(input.size()); - for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) - retval.emplace_back(ClipperPath_to_Slic3rPolyline(*it)); - return retval; + Polylines out; + out.reserve(polytree.Total()); + Inner::AddPolyNodeToPaths(polytree, out); + return out; } ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) { - // init Clipper ClipperLib::Clipper clipper; - clipper.Clear(); - - // perform union clipper.AddPaths(input, ClipperLib::ptSubject, true); ClipperLib::PolyTree polytree; clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero - - // write to ExPolygons object - return PolyTreeToExPolygons(polytree); + return PolyTreeToExPolygons(std::move(polytree)); } -ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) +// Offset outside by 10um, one by one. +template +static ClipperLib::Paths safety_offset(PathsProvider &&paths) { - ClipperLib::Path retval; - for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) - retval.emplace_back((*pit)(0), (*pit)(1)); - return retval; -} - -ClipperLib::Path Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input) -{ - ClipperLib::Path output; - output.reserve(input.points.size()); - for (Slic3r::Points::const_reverse_iterator pit = input.points.rbegin(); pit != input.points.rend(); ++pit) - output.emplace_back((*pit)(0), (*pit)(1)); - return output; -} - -ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input) -{ - ClipperLib::Paths retval; - for (Polygons::const_iterator it = input.begin(); it != input.end(); ++it) - retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(*it)); - return retval; -} - -ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input) -{ - ClipperLib::Paths retval; - for (auto &ep : input) { - retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(ep.contour)); - - for (auto &h : ep.holes) - retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(h)); + ClipperLib::ClipperOffset co; + ClipperLib::Paths out; + out.reserve(paths.size()); + ClipperLib::Paths out_this; + for (const ClipperLib::Path &path : paths) { + co.Clear(); + co.MiterLimit = 2.; + co.AddPath(path, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); + co.Execute(out_this, ClipperSafetyOffset); + append(out, std::move(out_this)); } - - return retval; + return out; } -ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input) +static ClipperLib::Paths safety_offset(const ClipperLib::Paths &paths) { - ClipperLib::Paths retval; - for (Polylines::const_iterator it = input.begin(); it != input.end(); ++it) - retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(*it)); - return retval; + return safety_offset(paths); } -ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) +static void safety_offset(ClipperLib::Paths *paths) +{ + *paths = safety_offset(*paths); +} + +template +ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { -#ifdef CLIPPERUTILS_OFFSET_SCALE - // scale input - scaleClipperPolygons(input); -#endif // CLIPPERUTILS_OFFSET_SCALE - // perform offset ClipperLib::ClipperOffset co; if (joinType == jtRound) co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; -#ifdef CLIPPERUTILS_OFFSET_SCALE - float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); -#else // CLIPPERUTILS_OFFSET_SCALE float delta_scaled = delta; -#endif // CLIPPERUTILS_OFFSET_SCALE co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPaths(input, joinType, endType); + co.AddPaths(std::forward(input), joinType, endType); ClipperLib::Paths retval; co.Execute(retval, delta_scaled); - -#ifdef CLIPPERUTILS_OFFSET_SCALE - // unscale output - unscaleClipperPolygons(retval); -#endif // CLIPPERUTILS_OFFSET_SCALE return retval; } -ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) -{ - ClipperLib::Paths paths; - paths.emplace_back(std::move(input)); - return _offset(std::move(paths), endType, delta, joinType, miterLimit); -} +Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(_offset(ClipperUtils::SinglePathProvider(polygon.points), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } + +#ifdef CLIPPERUTILS_UNSAFE_OFFSET +Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +#endif // CLIPPERUTILS_UNSAFE_OFFSET + +Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(_offset(ClipperUtils::SinglePathProvider(polyline.points), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(_offset(ClipperUtils::PolylinesProvider(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } + +#ifdef CLIPPERUTILS_UNSAFE_OFFSET +Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +#endif // CLIPPERUTILS_UNSAFE_OFFSET // This is a safe variant of the polygon offset, tailored for a single ExPolygon: // a single polygon with multiple non-overlapping holes. @@ -267,28 +196,16 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, { // printf("new ExPolygon offset\n"); // 1) Offset the outer contour. -#ifdef CLIPPERUTILS_OFFSET_SCALE - float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); -#else // CLIPPERUTILS_OFFSET_SCALE float delta_scaled = delta; -#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::Paths contours; { - ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(expolygon.contour); -#ifdef CLIPPERUTILS_OFFSET_SCALE - scaleClipperPolygon(input); -#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) -#ifdef CLIPPERUTILS_OFFSET_SCALE - co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); -#else // CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit; -#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPath(input, joinType, ClipperLib::etClosedPolygon); + co.AddPath(expolygon.contour.points, joinType, ClipperLib::etClosedPolygon); co.Execute(contours, delta_scaled); } @@ -297,22 +214,17 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, { holes.reserve(expolygon.holes.size()); for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) { - ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); -#ifdef CLIPPERUTILS_OFFSET_SCALE - scaleClipperPolygon(input); -#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) -#ifdef CLIPPERUTILS_OFFSET_SCALE - co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); -#else // CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit; -#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPath(input, joinType, ClipperLib::etClosedPolygon); + co.AddPath(it_hole->points, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths out; + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. co.Execute(out, - delta_scaled); append(holes, std::move(out)); } @@ -330,10 +242,6 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } - // 4) Unscale the output. -#ifdef CLIPPERUTILS_OFFSET_SCALE - unscaleClipperPolygons(output); -#endif // CLIPPERUTILS_OFFSET_SCALE return output; } @@ -343,76 +251,59 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { -#ifdef CLIPPERUTILS_OFFSET_SCALE - float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); -#else // CLIPPERUTILS_OFFSET_SCALE float delta_scaled = delta; -#endif // CLIPPERUTILS_OFFSET_SCALE // Offsetted ExPolygons before they are united. ClipperLib::Paths contours_cummulative; contours_cummulative.reserve(expolygons.size()); // How many non-empty offsetted expolygons were actually collected into contours_cummulative? // If only one, then there is no need to do a final union. size_t expolygons_collected = 0; - for (Slic3r::ExPolygons::const_iterator it_expoly = expolygons.begin(); it_expoly != expolygons.end(); ++ it_expoly) { + for (const Slic3r::ExPolygon &expoly : expolygons) { // 1) Offset the outer contour. ClipperLib::Paths contours; { - ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(it_expoly->contour); -#ifdef CLIPPERUTILS_OFFSET_SCALE - scaleClipperPolygon(input); -#endif // CLIPPERUTILS_OFFSET_SCALE ClipperLib::ClipperOffset co; if (joinType == jtRound) -#ifdef CLIPPERUTILS_OFFSET_SCALE - co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); -#else // CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit; -#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPath(input, joinType, ClipperLib::etClosedPolygon); + co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); co.Execute(contours, delta_scaled); } if (contours.empty()) // No need to try to offset the holes. continue; - if (it_expoly->holes.empty()) { + if (expoly.holes.empty()) { // No need to subtract holes from the offsetted expolygon, we are done. - contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + append(contours_cummulative, std::move(contours)); ++ expolygons_collected; } else { // 2) Offset the holes one by one, collect the offsetted holes. ClipperLib::Paths holes; { - for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) { - ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); -#ifdef CLIPPERUTILS_OFFSET_SCALE - scaleClipperPolygon(input); -#endif // CLIPPERUTILS_OFFSET_SCALE + for (const Polygon &hole : expoly.holes) { ClipperLib::ClipperOffset co; if (joinType == jtRound) -#ifdef CLIPPERUTILS_OFFSET_SCALE - co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); -#else // CLIPPERUTILS_OFFSET_SCALE co.ArcTolerance = miterLimit; -#endif // CLIPPERUTILS_OFFSET_SCALE else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPath(input, joinType, ClipperLib::etClosedPolygon); + co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths out; + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. co.Execute(out, - delta_scaled); - holes.insert(holes.end(), out.begin(), out.end()); + append(holes, std::move(out)); } } // 3) Subtract holes from the contours. if (holes.empty()) { // No hole remaining after an offset. Just copy the outer contour. - contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + append(contours_cummulative, std::move(contours)); ++ expolygons_collected; } else if (delta < 0) { // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. @@ -424,7 +315,7 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt ClipperLib::Paths output; clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); if (! output.empty()) { - contours_cummulative.insert(contours_cummulative.end(), output.begin(), output.end()); + append(contours_cummulative, std::move(output)); ++ expolygons_collected; } else { // The offsetted holes have eaten up the offsetted outer contour. @@ -434,11 +325,11 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt // area than the original hole or even disappear, therefore there will be no new intersections. // Just collect the reversed holes. contours_cummulative.reserve(contours.size() + holes.size()); - contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + append(contours_cummulative, std::move(contours)); // Reverse the holes in place. for (size_t i = 0; i < holes.size(); ++ i) std::reverse(holes[i].begin(), holes[i].end()); - contours_cummulative.insert(contours_cummulative.end(), holes.begin(), holes.end()); + append(contours_cummulative, std::move(holes)); ++ expolygons_collected; } } @@ -457,25 +348,11 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delt output = std::move(contours_cummulative); } -#ifdef CLIPPERUTILS_OFFSET_SCALE - // 4) Unscale the output. - unscaleClipperPolygons(output); -#endif // CLIPPERUTILS_OFFSET_SCALE return output; } -ClipperLib::Paths -_offset2(const Polygons &polygons, const float delta1, const float delta2, - const ClipperLib::JoinType joinType, const double miterLimit) +ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) { - // read input - ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons); - -#ifdef CLIPPERUTILS_OFFSET_SCALE - // scale input - scaleClipperPolygons(input); -#endif // CLIPPERUTILS_OFFSET_SCALE - // prepare ClipperOffset object ClipperLib::ClipperOffset co; if (joinType == jtRound) { @@ -483,18 +360,13 @@ _offset2(const Polygons &polygons, const float delta1, const float delta2, } else { co.MiterLimit = miterLimit; } -#ifdef CLIPPERUTILS_OFFSET_SCALE - float delta_scaled1 = delta1 * float(CLIPPER_OFFSET_SCALE); - float delta_scaled2 = delta2 * float(CLIPPER_OFFSET_SCALE); -#else // CLIPPERUTILS_OFFSET_SCALE float delta_scaled1 = delta1; float delta_scaled2 = delta2; -#endif // CLIPPERUTILS_OFFSET_SCALE co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR); // perform first offset ClipperLib::Paths output1; - co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); + co.AddPaths(ClipperUtils::PolygonsProvider(polygons), joinType, ClipperLib::etClosedPolygon); co.Execute(output1, delta_scaled1); // perform second offset @@ -503,33 +375,17 @@ _offset2(const Polygons &polygons, const float delta1, const float delta2, ClipperLib::Paths retval; co.Execute(retval, delta_scaled2); -#ifdef CLIPPERUTILS_OFFSET_SCALE - // unscale output - unscaleClipperPolygons(retval); -#endif // CLIPPERUTILS_OFFSET_SCALE return retval; } -Polygons -offset2(const Polygons &polygons, const float delta1, const float delta2, - const ClipperLib::JoinType joinType, const double miterLimit) +Polygons offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) { - // perform offset - ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit); - - // convert into ExPolygons - return ClipperPaths_to_Slic3rPolygons(output); + return to_polygons(_offset2(polygons, delta1, delta2, joinType, miterLimit)); } -ExPolygons -offset2_ex(const Polygons &polygons, const float delta1, const float delta2, - const ClipperLib::JoinType joinType, const double miterLimit) +ExPolygons offset2_ex(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) { - // perform offset - ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit); - - // convert into ExPolygons - return ClipperPaths_to_Slic3rExPolygons(output); + return ClipperPaths_to_Slic3rExPolygons(_offset2(polygons, delta1, delta2, joinType, miterLimit)); } //FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper @@ -545,64 +401,57 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, return union_ex(polys); } -template -T _clipper_do(const ClipperLib::ClipType clipType, - TSubj && subject, - TClip && clip, - const ClipperLib::PolyFillType fillType, - const bool safety_offset_) +template +TResult _clipper_do( + const ClipperLib::ClipType clipType, + TSubj && subject, + TClip && clip, + const ClipperLib::PolyFillType fillType) { - // read input - ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(std::forward(subject)); - ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(std::forward(clip)); - - // perform safety offset - if (safety_offset_) { - if (clipType == ClipperLib::ctUnion) { - safety_offset(&input_subject); - } else { - safety_offset(&input_clip); - } - } - - // init Clipper ClipperLib::Clipper clipper; - clipper.Clear(); - - // add polygons - clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); - clipper.AddPaths(input_clip, ClipperLib::ptClip, true); - - // perform operation - T retval; + clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); + clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); + TResult retval; clipper.Execute(clipType, retval, fillType, fillType); return retval; } +template +TResult _clipper_do( + const ClipperLib::ClipType clipType, + TSubj && subject, + TClip && clip, + const ClipperLib::PolyFillType fillType, + const bool do_safety_offset) +{ + return do_safety_offset ? + (clipType == ClipperLib::ctUnion ? + _clipper_do(clipType, safety_offset(std::forward(subject)), std::forward(clip), fillType) : + _clipper_do(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType)) : + _clipper_do(clipType, std::forward(subject), std::forward(clip), fillType); +} + // Fix of #117: A large fractal pyramid takes ages to slice // The Clipper library has difficulties processing overlapping polygons. // Namely, the function ClipperLib::JoinCommonEdges() has potentially a terrible time complexity if the output // of the operation is of the PolyTree type. -// This function implmenets a following workaround: +// This function implemenets a following workaround: // 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time. // 2) Run Clipper Union once again to extract the PolyTree from the result of 1). -inline ClipperLib::PolyTree _clipper_do_polytree2(const ClipperLib::ClipType clipType, const Polygons &subject, - const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) +template +inline ClipperLib::PolyTree _clipper_do_polytree2( + const ClipperLib::ClipType clipType, + PathProvider1 &&subject, + PathProvider2 &&clip, + const ClipperLib::PolyFillType fillType) { - // read input - ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); - ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); - - // perform safety offset - if (safety_offset_) - safety_offset((clipType == ClipperLib::ctUnion) ? &input_subject : &input_clip); - ClipperLib::Clipper clipper; - clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); - clipper.AddPaths(input_clip, ClipperLib::ptClip, true); + clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); + clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); // Perform the operation with the output to input_subject. // This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library // if there are overapping edges. + ClipperLib::Paths input_subject; clipper.Execute(clipType, input_subject, fillType, fillType); // Perform an additional Union operation to generate the PolyTree ordering. clipper.Clear(); @@ -611,51 +460,75 @@ inline ClipperLib::PolyTree _clipper_do_polytree2(const ClipperLib::ClipType cli clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType); return retval; } - -ClipperLib::PolyTree _clipper_do_pl(const ClipperLib::ClipType clipType, const Polylines &subject, - const Polygons &clip, const ClipperLib::PolyFillType fillType, - const bool safety_offset_) +template +inline ClipperLib::PolyTree _clipper_do_polytree2( + const ClipperLib::ClipType clipType, + PathProvider1 &&subject, + PathProvider2 &&clip, + const ClipperLib::PolyFillType fillType, + const bool do_safety_offset) +{ + return do_safety_offset ? + (clipType == ClipperLib::ctUnion ? + _clipper_do_polytree2(clipType, safety_offset(std::forward(subject)), std::forward(clip), fillType) : + _clipper_do_polytree2(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType)) : + _clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), fillType); +} + +template +static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, bool do_safety_offset) +{ + return to_polygons(_clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); +} + +Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool do_safety_offset) + { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), do_safety_offset); } +Slic3r::Polygons union_(const Slic3r::ExPolygons &subject, bool do_safety_offset) + { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), do_safety_offset); } +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool do_safety_offset) + { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), do_safety_offset); } + +template +static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, bool do_safety_offset) + { return PolyTreeToExPolygons(_clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); } + +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), do_safety_offset); } +Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& subject) + { return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } +Slic3r::ExPolygons union_ex(const Slic3r::Surfaces& subject) + { return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } + +Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, const Polygons &clip, bool do_safety_offset) { - // read input - ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); - ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); - - // perform safety offset - if (safety_offset_) safety_offset(&input_clip); - - // init Clipper ClipperLib::Clipper clipper; - clipper.Clear(); - - // add polygons - clipper.AddPaths(input_subject, ClipperLib::ptSubject, false); - clipper.AddPaths(input_clip, ClipperLib::ptClip, true); - - // perform operation + clipper.AddPaths(ClipperUtils::PolylinesProvider(subject), ClipperLib::ptSubject, false); + if (do_safety_offset) + clipper.AddPaths(safety_offset(ClipperUtils::PolygonsProvider(clip)), ClipperLib::ptClip, true); + else + clipper.AddPaths(ClipperUtils::PolygonsProvider(clip), ClipperLib::ptClip, true); ClipperLib::PolyTree retval; - clipper.Execute(clipType, retval, fillType, fillType); - return retval; + clipper.Execute(clipType, retval, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + return PolyTreeToPolylines(std::move(retval)); } -Polygons _clipper(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_) -{ - return ClipperPaths_to_Slic3rPolygons(_clipper_do(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_)); -} - -ExPolygons _clipper_ex(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_) -{ - ClipperLib::PolyTree polytree = _clipper_do_polytree2(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_); - return PolyTreeToExPolygons(polytree); -} - -Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, const Polygons &clip, bool safety_offset_) -{ - ClipperLib::Paths output; - ClipperLib::PolyTreeToPaths(_clipper_do_pl(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_), output); - return ClipperPaths_to_Slic3rPolylines(output); -} - -Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_) +Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool do_safety_offset) { // transform input polygons into polylines Polylines polylines; @@ -664,7 +537,7 @@ Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, co polylines.emplace_back(polygon->operator Polyline()); // implicit call to split_at_first_point() // perform clipping - Polylines retval = _clipper_pl(clipType, polylines, clip, safety_offset_); + Polylines retval = _clipper_pl(clipType, polylines, clip, do_safety_offset); /* If the split_at_first_point() call above happens to split the polygon inside the clipping area we would get two consecutive polylines instead of a single one, so we go through them in order @@ -703,9 +576,7 @@ Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, co return retval; } -Lines -_clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, - bool safety_offset_) +Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, bool do_safety_offset) { // convert Lines to Polylines Polylines polylines; @@ -714,7 +585,7 @@ _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons polylines.emplace_back(Polyline(line.a, line.b)); // perform operation - polylines = _clipper_pl(clipType, polylines, clip, safety_offset_); + polylines = _clipper_pl(clipType, polylines, clip, do_safety_offset); // convert Polylines to Lines Lines retval; @@ -723,24 +594,14 @@ _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons return retval; } -ClipperLib::PolyTree union_pt(const Polygons &subject, bool safety_offset_) +ClipperLib::PolyTree union_pt(const Polygons &subject) { - return _clipper_do(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); + return _clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); } -ClipperLib::PolyTree union_pt(const ExPolygons &subject, bool safety_offset_) +ClipperLib::PolyTree union_pt(const ExPolygons &subject) { - return _clipper_do(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); -} - -ClipperLib::PolyTree union_pt(Polygons &&subject, bool safety_offset_) -{ - return _clipper_do(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_); -} - -ClipperLib::PolyTree union_pt(ExPolygons &&subject, bool safety_offset_) -{ - return _clipper_do(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_); + return _clipper_do(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); } // Simple spatial ordering of Polynodes @@ -766,7 +627,7 @@ static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *ou foreach_node(nodes, [&out](const ClipperLib::PolyNode *node) { traverse_pt_noholes(node->Childs, out); - out->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); + out->emplace_back(node->Contour); if (node->IsHole()) out->back().reverse(); // ccw }); } @@ -782,7 +643,7 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons // Perform the ordering, push results recursively. //FIXME pass the last point to chain_clipper_polynodes? for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) { - retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); + retval->emplace_back(node->Contour); if (node->IsHole()) // Orient a hole, which is clockwise oriented, to CCW. retval->back().reverse(); @@ -791,9 +652,9 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons } } -Polygons union_pt_chained_outside_in(const Polygons &subject, bool safety_offset_) +Polygons union_pt_chained_outside_in(const Polygons &subject) { - ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); + ClipperLib::PolyTree polytree = union_pt(subject); Polygons retval; traverse_pt_outside_in(polytree.Childs, &retval); @@ -802,22 +663,19 @@ Polygons union_pt_chained_outside_in(const Polygons &subject, bool safety_offset Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) { - // convert into Clipper polygons - ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); - ClipperLib::Paths output; if (preserve_collinear) { ClipperLib::Clipper c; c.PreserveCollinear(true); c.StrictlySimple(true); - c.AddPaths(input_subject, ClipperLib::ptSubject, true); + c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true); c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } else { - ClipperLib::SimplifyPolygons(input_subject, output, ClipperLib::pftNonZero); + output = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(subject), ClipperLib::pftNonZero); } // convert into Slic3r polygons - return ClipperPaths_to_Slic3rPolygons(output); + return to_polygons(std::move(output)); } ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear) @@ -825,76 +683,15 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear if (! preserve_collinear) return union_ex(simplify_polygons(subject, false)); - // convert into Clipper polygons - ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); - - ClipperLib::PolyTree polytree; - + ClipperLib::PolyTree polytree; ClipperLib::Clipper c; c.PreserveCollinear(true); c.StrictlySimple(true); - c.AddPaths(input_subject, ClipperLib::ptSubject, true); + c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true); c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); // convert into ExPolygons - return PolyTreeToExPolygons(polytree); -} - -void safety_offset(ClipperLib::Paths* paths) -{ - CLIPPERUTILS_PROFILE_FUNC(); - -#ifdef CLIPPERUTILS_OFFSET_SCALE - // scale input - scaleClipperPolygons(*paths); -#endif // CLIPPERUTILS_OFFSET_SCALE - - // perform offset (delta = scale 1e-05) - ClipperLib::ClipperOffset co; -#ifdef CLIPPER_UTILS_DEBUG - if (clipper_export_enabled) { - static int iRun = 0; - export_clipper_input_polygons_bin(debug_out_path("safety_offset-polygons-%d", ++iRun).c_str(), *paths, ClipperLib::Paths()); - } -#endif /* CLIPPER_UTILS_DEBUG */ - ClipperLib::Paths out; - for (size_t i = 0; i < paths->size(); ++ i) { - ClipperLib::Path &path = (*paths)[i]; - co.Clear(); - co.MiterLimit = 2; - bool ccw = ClipperLib::Orientation(path); - if (! ccw) - std::reverse(path.begin(), path.end()); - { - CLIPPERUTILS_PROFILE_BLOCK(safety_offset_AddPaths); - co.AddPath((*paths)[i], ClipperLib::jtMiter, ClipperLib::etClosedPolygon); - } - { - CLIPPERUTILS_PROFILE_BLOCK(safety_offset_Execute); - // offset outside by 10um - ClipperLib::Paths out_this; -#ifdef CLIPPERUTILS_OFFSET_SCALE - co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE)); -#else // CLIPPERUTILS_OFFSET_SCALE - co.Execute(out_this, ccw ? 10.f : -10.f); -#endif // CLIPPERUTILS_OFFSET_SCALE - if (! ccw) { - // Reverse the resulting contours once again. - for (ClipperLib::Paths::iterator it = out_this.begin(); it != out_this.end(); ++ it) - std::reverse(it->begin(), it->end()); - } - if (out.empty()) - out = std::move(out_this); - else - std::move(std::begin(out_this), std::end(out_this), std::back_inserter(out)); - } - } - *paths = std::move(out); - -#ifdef CLIPPERUTILS_OFFSET_SCALE - // unscale output - unscaleClipperPolygons(*paths); -#endif // CLIPPERUTILS_OFFSET_SCALE + return PolyTreeToExPolygons(std::move(polytree)); } Polygons top_level_islands(const Slic3r::Polygons &polygons) @@ -903,14 +700,14 @@ Polygons top_level_islands(const Slic3r::Polygons &polygons) ClipperLib::Clipper clipper; clipper.Clear(); // perform union - clipper.AddPaths(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::ptSubject, true); + clipper.AddPaths(ClipperUtils::PolygonsProvider(polygons), ClipperLib::ptSubject, true); ClipperLib::PolyTree polytree; clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // Convert only the top level islands to the output. Polygons out; out.reserve(polytree.ChildCount()); for (int i = 0; i < polytree.ChildCount(); ++i) - out.emplace_back(ClipperPath_to_Slic3rPolygon(polytree.Childs[i]->Contour)); + out.emplace_back(std::move(polytree.Childs[i]->Contour)); return out; } @@ -988,9 +785,6 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v // Add a new point to the output, scale by CLIPPER_OFFSET_SCALE and round to ClipperLib::cInt. auto add_offset_point = [&out](Vec2d pt) { -#ifdef CLIPPERUTILS_OFFSET_SCALE - pt *= double(CLIPPER_OFFSET_SCALE); -#endif // CLIPPERUTILS_OFFSET_SCALE pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0)); out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y())); }; @@ -1086,7 +880,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v { ClipperLib::Path polytmp(out); unscaleClipperPolygon(polytmp); - Slic3r::Polygon offsetted = ClipperPath_to_Slic3rPolygon(polytmp); + Slic3r::Polygon offsetted(std::move(polytmp)); BoundingBox bbox = get_extents(contour); bbox.merge(get_extents(offsetted)); static int iRun = 0; @@ -1140,11 +934,7 @@ Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit) @@ -1186,11 +976,7 @@ for (const std::vector& ds : deltas) clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } -#ifdef CLIPPERUTILS_OFFSET_SCALE - // 4) Unscale the output. - unscaleClipperPolygons(output); -#endif // CLIPPERUTILS_OFFSET_SCALE - return ClipperPaths_to_Slic3rPolygons(output); + return to_polygons(std::move(output)); } ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit) @@ -1221,24 +1007,18 @@ for (const std::vector& ds : deltas) #endif /* NDEBUG */ // 3) Subtract holes from the contours. -#ifdef CLIPPERUTILS_OFFSET_SCALE - unscaleClipperPolygons(contours); -#endif // CLIPPERUTILS_OFFSET_SCALE ExPolygons output; if (holes.empty()) { output.reserve(contours.size()); for (ClipperLib::Path &path : contours) - output.emplace_back(ClipperPath_to_Slic3rPolygon(path)); + output.emplace_back(std::move(path)); } else { ClipperLib::Clipper clipper; -#ifdef CLIPPERUTILS_OFFSET_SCALE - unscaleClipperPolygons(holes); -#endif // CLIPPERUTILS_OFFSET_SCALE clipper.AddPaths(contours, ClipperLib::ptSubject, true); clipper.AddPaths(holes, ClipperLib::ptClip, true); ClipperLib::PolyTree polytree; clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - output = PolyTreeToExPolygons(polytree); + output = PolyTreeToExPolygons(std::move(polytree)); } return output; @@ -1273,24 +1053,18 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector(nullptr); } + // all iterators point to end. + constexpr bool operator==(const iterator &rhs) const { return true; } + constexpr bool operator!=(const iterator &rhs) const { return false; } + constexpr const Points& operator++(int) { assert(false); return *static_cast(nullptr); } + constexpr iterator& operator++() { assert(false); return *this; } + }; + + constexpr EmptyPathsProvider() {} + static constexpr iterator cend() throw() { return iterator{}; } + static constexpr iterator end() throw() { return cend(); } + static constexpr iterator cbegin() throw() { return cend(); } + static constexpr iterator begin() throw() { return cend(); } + static constexpr size_t size() throw() { return 0; } + }; + + class SinglePathProvider { + public: + SinglePathProvider(const Points &points) : m_points(points) {} + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(const Points &points) : m_ptr(&points) {} + const Points& operator*() const { return *m_ptr; } + bool operator==(const iterator &rhs) const { return m_ptr == rhs.m_ptr; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + const Points& operator++(int) { auto out = m_ptr; m_ptr = &s_end; return *out; } + iterator& operator++() { m_ptr = &s_end; return *this; } + private: + const Points *m_ptr; + }; + + iterator cbegin() const { return iterator(m_points); } + iterator begin() const { return this->cbegin(); } + iterator cend() const { return iterator(s_end); } + iterator end() const { return this->cend(); } + size_t size() const { return 1; } + + private: + const Points &m_points; + static Points s_end; + }; + + template + class MultiPointsProvider { + public: + MultiPointsProvider(const std::vector &multipoints) : m_multipoints(multipoints) {} + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(typename std::vector::const_iterator it) : m_it(it) {} + const Points& operator*() const { return m_it->points; } + bool operator==(const iterator &rhs) const { return m_it == rhs.m_it; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + const Points& operator++(int) { return (m_it ++)->points; } + iterator& operator++() { ++ m_it; return *this; } + private: + typename std::vector::const_iterator m_it; + }; + + iterator cbegin() const { return iterator(m_multipoints.begin()); } + iterator begin() const { return this->cbegin(); } + iterator cend() const { return iterator(m_multipoints.end()); } + iterator end() const { return this->cend(); } + size_t size() const { return m_multipoints.size(); } + + private: + const std::vector &m_multipoints; + }; + + using PolygonsProvider = MultiPointsProvider; + using PolylinesProvider = MultiPointsProvider; + + struct ExPolygonProvider { + ExPolygonProvider(const ExPolygon &expoly) : m_expoly(expoly) {} + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(const ExPolygon &expoly, int idx) : m_expoly(expoly), m_idx(idx) {} + const Points& operator*() const { return (m_idx == 0) ? m_expoly.contour.points : m_expoly.holes[m_idx - 1].points; } + bool operator==(const iterator &rhs) const { assert(m_expoly == rhs.m_expoly); return m_idx == rhs.m_idx; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + const Points& operator++(int) { const Points &out = **this; ++ m_idx; return out; } + iterator& operator++() { ++ m_idx; return *this; } + private: + const ExPolygon &m_expoly; + int m_idx; + }; + + iterator cbegin() const { return iterator(m_expoly, 0); } + iterator begin() const { return this->cbegin(); } + iterator cend() const { return iterator(m_expoly, m_expoly.holes.size() + 1); } + iterator end() const { return this->cend(); } + size_t size() const { return m_expoly.holes.size() + 1; } + + private: + const ExPolygon &m_expoly; + }; + + struct ExPolygonsProvider { + ExPolygonsProvider(const ExPolygons &expolygons) : m_expolygons(expolygons) { + m_size = 0; + for (const ExPolygon &expoly : expolygons) + m_size += expoly.holes.size() + 1; + } + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(ExPolygons::const_iterator it) : m_it_expolygon(it), m_idx_contour(0) {} + const Points& operator*() const { return (m_idx_contour == 0) ? m_it_expolygon->contour.points : m_it_expolygon->holes[m_idx_contour - 1].points; } + bool operator==(const iterator &rhs) const { return m_it_expolygon == rhs.m_it_expolygon && m_idx_contour == rhs.m_idx_contour; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + iterator& operator++() { + if (++ m_idx_contour == m_it_expolygon->holes.size() + 1) { + ++ m_it_expolygon; + m_idx_contour = 0; + } + return *this; + } + const Points& operator++(int) { + const Points &out = **this; + ++ (*this); + return out; + } + private: + ExPolygons::const_iterator m_it_expolygon; + int m_idx_contour; + }; + + iterator cbegin() const { return iterator(m_expolygons.cbegin()); } + iterator begin() const { return this->cbegin(); } + iterator cend() const { return iterator(m_expolygons.cend()); } + iterator end() const { return this->cend(); } + size_t size() const { return m_size; } + + private: + const ExPolygons &m_expolygons; + size_t m_size; + }; + + struct SurfacesProvider { + SurfacesProvider(const Surfaces &surfaces) : m_surfaces(surfaces) { + m_size = 0; + for (const Surface &surface : surfaces) + m_size += surface.expolygon.holes.size() + 1; + } + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(Surfaces::const_iterator it) : m_it_surface(it), m_idx_contour(0) {} + const Points& operator*() const { return (m_idx_contour == 0) ? m_it_surface->expolygon.contour.points : m_it_surface->expolygon.holes[m_idx_contour - 1].points; } + bool operator==(const iterator &rhs) const { return m_it_surface == rhs.m_it_surface && m_idx_contour == rhs.m_idx_contour; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + iterator& operator++() { + if (++ m_idx_contour == m_it_surface->expolygon.holes.size() + 1) { + ++ m_it_surface; + m_idx_contour = 0; + } + return *this; + } + const Points& operator++(int) { + const Points &out = **this; + ++ (*this); + return out; + } + private: + Surfaces::const_iterator m_it_surface; + int m_idx_contour; + }; + + iterator cbegin() const { return iterator(m_surfaces.cbegin()); } + iterator begin() const { return this->cbegin(); } + iterator cend() const { return iterator(m_surfaces.cend()); } + iterator end() const { return this->cend(); } + size_t size() const { return m_size; } + + private: + const Surfaces &m_surfaces; + size_t m_size; + }; +} + +ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); // offset Polygons -ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit); -ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit); -inline Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); #ifdef CLIPPERUTILS_UNSAFE_OFFSET -inline Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); #endif // CLIPPERUTILS_UNSAFE_OFFSET // offset Polylines -inline Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3) - { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polyline), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } -inline Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3) - { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } +Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); // offset expolygons and surfaces ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit); ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit); inline Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return ClipperPaths_to_Slic3rPolygons(_offset(expolygon, delta, joinType, miterLimit)); } + { return to_polygons(_offset(expolygon, delta, joinType, miterLimit)); } inline Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return ClipperPaths_to_Slic3rPolygons(_offset(expolygons, delta, joinType, miterLimit)); } -inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } + { return to_polygons(_offset(expolygons, delta, joinType, miterLimit)); } #ifdef CLIPPERUTILS_UNSAFE_OFFSET -inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); #endif // CLIPPERUTILS_UNSAFE_OFFSET inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) @@ -101,141 +260,68 @@ Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -Slic3r::Polygons _clipper(ClipperLib::ClipType clipType, - const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); -Slic3r::ExPolygons _clipper_ex(ClipperLib::ClipType clipType, - const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); -Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, - const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); -Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, - const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); -Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, - const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -// diff -inline Slic3r::Polygons -diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); + +inline Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { - return _clipper(ClipperLib::ctDifference, subject, clip, safety_offset_); + return _clipper_pl(ClipperLib::ctDifference, subject, clip, do_safety_offset); } -inline Slic3r::ExPolygons -diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +inline Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { - return _clipper_ex(ClipperLib::ctDifference, subject, clip, safety_offset_); + return _clipper_pl(ClipperLib::ctDifference, subject, clip, do_safety_offset); } -inline Slic3r::ExPolygons -diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { - return _clipper_ex(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_); + return _clipper_ln(ClipperLib::ctDifference, subject, clip, do_safety_offset); } -inline Slic3r::Polygons -diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); + +inline Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { - return _clipper(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_); + return _clipper_pl(ClipperLib::ctIntersection, subject, clip, do_safety_offset); } -inline Slic3r::Polylines -diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +inline Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { - return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_); + return _clipper_pl(ClipperLib::ctIntersection, subject, clip, do_safety_offset); } -inline Slic3r::Polylines -diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { - return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_); + return _clipper_ln(ClipperLib::ctIntersection, subject, clip, do_safety_offset); } -inline Slic3r::Lines -diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) -{ - return _clipper_ln(ClipperLib::ctDifference, subject, clip, safety_offset_); -} - -// intersection -inline Slic3r::Polygons -intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) -{ - return _clipper(ClipperLib::ctIntersection, subject, clip, safety_offset_); -} - -inline Slic3r::ExPolygons -intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) -{ - return _clipper_ex(ClipperLib::ctIntersection, subject, clip, safety_offset_); -} - -inline Slic3r::ExPolygons -intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) -{ - return _clipper_ex(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_); -} - -inline Slic3r::Polygons -intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) -{ - return _clipper(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_); -} - -inline Slic3r::Polylines -intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) -{ - return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_); -} - -inline Slic3r::Polylines -intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) -{ - return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_); -} - -inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) -{ - return _clipper_ln(ClipperLib::ctIntersection, subject, clip, safety_offset_); -} - -inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { Slic3r::Lines lines; lines.emplace_back(subject); - return _clipper_ln(ClipperLib::ctIntersection, lines, clip, safety_offset_); + return _clipper_ln(ClipperLib::ctIntersection, lines, clip, do_safety_offset); } -// union -inline Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset_ = false) -{ - return _clipper(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); -} +Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool do_safety_offset = false); +Slic3r::Polygons union_(const Slic3r::ExPolygons &subject, bool do_safety_offset = false); +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool do_safety_offset = false); +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool do_safety_offset = false); +Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); +Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject); -inline Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool safety_offset_ = false) -{ - return _clipper(ClipperLib::ctUnion, subject, subject2, safety_offset_); -} +ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject); +ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject); -inline Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset_ = false) -{ - return _clipper_ex(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); -} - -inline Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, bool safety_offset_ = false) -{ - return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); -} - -inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_offset_ = false) -{ - return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); -} - -ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false); -ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject, bool safety_offset_ = false); -ClipperLib::PolyTree union_pt(Slic3r::Polygons &&subject, bool safety_offset_ = false); -ClipperLib::PolyTree union_pt(Slic3r::ExPolygons &&subject, bool safety_offset_ = false); - -Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject, bool safety_offset_ = false); +Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject); ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes); @@ -283,7 +369,7 @@ void traverse_pt(const ClipperLib::PolyNode *tree, Polygons *out) if (!tree) return; // terminates recursion // Push the contour of the current level - out->emplace_back(ClipperPath_to_Slic3rPolygon(tree->Contour)); + out->emplace_back(tree->Contour); // Do the recursion for all the children. traverse_pt(tree->Childs, out); @@ -302,13 +388,13 @@ void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *out) } ExPolygon level; - level.contour = ClipperPath_to_Slic3rPolygon(tree->Contour); + level.contour.points = tree->Contour; foreach_node(tree->Childs, [out, &level] (const ClipperLib::PolyNode *node) { // Holes are collected here. - level.holes.emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); + level.holes.emplace_back(node->Contour); // By doing a recursion, a new level expoly is created with the contour // and holes of the lower level. Doing this for all the childs. @@ -331,8 +417,6 @@ void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval) Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false); -void safety_offset(ClipperLib::Paths* paths); - Polygons top_level_islands(const Slic3r::Polygons &polygons); ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector &deltas, double miter_limit); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index fcf3c159ece..04b84f7670a 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -83,8 +83,8 @@ inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs. inline size_t number_polygons(const ExPolygons &expolys) { size_t n_polygons = 0; - for (ExPolygons::const_iterator it = expolys.begin(); it != expolys.end(); ++ it) - n_polygons += it->holes.size() + 1; + for (const ExPolygon &ex : expolys) + n_polygons += ex.holes.size() + 1; return n_polygons; } diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 785c93be3b3..d5997552b97 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -33,7 +33,7 @@ void FillConcentric::_fill_surface_single( // generate paths from the outermost to the innermost, to avoid // adhesion problems of the first central tiny loops - loops = union_pt_chained_outside_in(loops, false); + loops = union_pt_chained_outside_in(loops); // split paths using a nearest neighbor search size_t iPathFirst = polylines_out.size(); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index e86a67b6fb2..857c98f52e8 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -185,7 +185,7 @@ void Layer::make_perimeters() } // merge the surfaces assigned to each group for (std::pair &surfaces_with_extra_perimeters : slices) - new_slices.append(offset_ex(to_expolygons(surfaces_with_extra_perimeters.second), 10.f), surfaces_with_extra_perimeters.second.front()); + new_slices.append(offset_ex(to_expolygons(surfaces_with_extra_perimeters.second), ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); } // make perimeters diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 7c588c67cc2..e744272ac0e 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -70,7 +70,7 @@ double Polygon::area() const bool Polygon::is_counter_clockwise() const { - return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); + return ClipperLib::Orientation(this->points); } bool Polygon::is_clockwise() const diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 93cd7012134..333f1e6b1ae 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -222,6 +222,24 @@ inline Polylines to_polylines(Polygons &&polys) return polylines; } +inline Polygons to_polygons(const std::vector &paths) +{ + Polygons out; + out.reserve(paths.size()); + for (const Points &path : paths) + out.emplace_back(path); + return out; +} + +inline Polygons to_polygons(std::vector &&paths) +{ + Polygons out; + out.reserve(paths.size()); + for (const Points &path : paths) + out.emplace_back(std::move(path)); + return out; +} + } // Slic3r // start Boost diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 9c70522bfbf..88f910590b0 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -124,6 +124,24 @@ inline Lines to_lines(const Polylines &polys) return lines; } +inline Polylines to_polylines(const std::vector &paths) +{ + Polylines out; + out.reserve(paths.size()); + for (const Points &path : paths) + out.emplace_back(path); + return out; +} + +inline Polylines to_polylines(std::vector &&paths) +{ + Polylines out; + out.reserve(paths.size()); + for (const Points &path : paths) + out.emplace_back(std::move(path)); + return out; +} + inline void polylines_append(Polylines &dst, const Polylines &src) { dst.insert(dst.end(), src.begin(), src.end()); diff --git a/src/libslic3r/SLA/ConcaveHull.cpp b/src/libslic3r/SLA/ConcaveHull.cpp index d3c0d102244..1724089894c 100644 --- a/src/libslic3r/SLA/ConcaveHull.cpp +++ b/src/libslic3r/SLA/ConcaveHull.cpp @@ -42,9 +42,10 @@ Point ConcaveHull::centroid(const Points &pp) // As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound // mode -static ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths, - coord_t delta, - ClipperLib::JoinType jointype) +template +static ClipperLib::Paths fast_offset(PolygonsProvider &&paths, + coord_t delta, + ClipperLib::JoinType jointype) { using ClipperLib::ClipperOffset; using ClipperLib::etClosedPolygon; @@ -61,7 +62,7 @@ static ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths, return {}; } - offs.AddPaths(paths, jointype, etClosedPolygon); + offs.AddPaths(std::forward(paths), jointype, etClosedPolygon); Paths result; offs.Execute(result, static_cast(delta)); @@ -157,11 +158,9 @@ ExPolygons ConcaveHull::to_expolygons() const ExPolygons offset_waffle_style_ex(const ConcaveHull &hull, coord_t delta) { - ClipperLib::Paths paths = Slic3rMultiPoints_to_ClipperPaths(hull.polygons()); - paths = fast_offset(paths, 2 * delta, ClipperLib::jtRound); - paths = fast_offset(paths, -delta, ClipperLib::jtRound); - ExPolygons ret = ClipperPaths_to_Slic3rExPolygons(paths); - for (ExPolygon &p : ret) p.holes = {}; + ExPolygons ret = ClipperPaths_to_Slic3rExPolygons( + fast_offset(fast_offset(ClipperUtils::PolygonsProvider(hull.polygons()), 2 * delta, ClipperLib::jtRound), -delta, ClipperLib::jtRound)); + for (ExPolygon &p : ret) p.holes.clear(); return ret; } diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index 927c325898e..e11914a1cb2 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -179,10 +179,10 @@ PadSkeleton divide_blueprint(const ExPolygons &bp) ret.outer.reserve(size_t(ptree.Total())); for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) { - ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour)); + ExPolygon poly; + poly.contour.points = std::move(node->Contour); for (ClipperLib::PolyTree::PolyNode *child : node->Childs) { - poly.holes.emplace_back( - ClipperPath_to_Slic3rPolygon(child->Contour)); + poly.holes.emplace_back(std::move(child->Contour)); traverse_pt(child->Childs, &ret.inner); } @@ -342,18 +342,18 @@ public: template ExPolygon offset_contour_only(const ExPolygon &poly, coord_t delta, Args...args) { - ExPolygons tmp = offset_ex(poly.contour, float(delta), args...); + Polygons tmp = offset(poly.contour, float(delta), args...); if (tmp.empty()) return {}; Polygons holes = poly.holes; for (auto &h : holes) h.reverse(); - tmp = diff_ex(to_polygons(tmp), holes); + ExPolygons tmp2 = diff_ex(tmp, holes); - if (tmp.empty()) return {}; + if (tmp2.empty()) return {}; - return tmp.front(); + return std::move(tmp2.front()); } bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg, diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index f8fa1ca17f1..da058331087 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1799,9 +1799,9 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, const float clos // append to the supplied collection if (safety_offset > 0) - expolygons_append(*slices, offset2_ex(union_ex(loops, false), +safety_offset, -safety_offset)); + expolygons_append(*slices, offset2_ex(union_ex(loops), +safety_offset, -safety_offset)); else - expolygons_append(*slices, union_ex(loops, false)); + expolygons_append(*slices, union_ex(loops)); } void TriangleMeshSlicer::make_expolygons(std::vector &lines, const float closing_radius, ExPolygons* slices) const diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 1c43e7eb044..29551ac15de 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -194,7 +194,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c const BoundingBox& bed_bbox = poly.contour.bounding_box(); calc_gridlines(poly, bed_bbox); - m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; + m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0]; reset(); m_texture.reset(); From 2e4b7c2c07f81a98ff36a5f0059e5ab3cb8dc0aa Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 30 Apr 2021 13:11:26 +0200 Subject: [PATCH 142/154] Improved MM priming lines placement on circular beds (#6459) --- src/libslic3r/GCode/WipeTower.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 86a6616ee2d..ae800a5ff09 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -639,9 +639,11 @@ std::vector WipeTower::prime( float prime_section_width = std::min(0.9f * m_bed_width / tools.size(), 60.f); box_coordinates cleaning_box(Vec2f(0.02f * m_bed_width, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); - // In case of a circular bed, place it so it goes across the diameter and hope it will fit - if (m_bed_shape == CircularBed) - cleaning_box.translate(-m_bed_width/2 + m_bed_width * 0.03f, -m_bed_width * 0.12f); + if (m_bed_shape == CircularBed) { + cleaning_box = box_coordinates(Vec2f(0.f, 0.f), prime_section_width, 100.f); + float total_width_half = tools.size() * prime_section_width / 2.f; + cleaning_box.translate(-total_width_half, -std::sqrt(std::max(0.f, std::pow(m_bed_width/2, 2.f) - std::pow(1.05f * total_width_half, 2.f)))); + } else cleaning_box.translate(m_bed_bottom_left); From a9009b321800e8a844c2c231ee8521c70896eb15 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 30 Apr 2021 16:49:13 +0200 Subject: [PATCH 143/154] OSX specific: Fixed scale of the frequently settings, when extra display is connected --- src/slic3r/GUI/Plater.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e94348fb226..c0cda004250 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -976,7 +976,6 @@ void Sidebar::sys_color_changed() for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); - p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_list->sys_color_changed(); p->object_manipulation->sys_color_changed(); From ac7b40d6952c3c3f9a793685b4bc6d7aaa6d48de Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 30 Apr 2021 16:54:19 +0200 Subject: [PATCH 144/154] Fixed a bug with selection from the 3D-scene when ObjectSettings item is selected in ObjectList Steps to repro: 1. Add 2 objects, add Settings for some of object -> Object Settings item is selected 2. In the 3D-scene select another object -> BUG: no changes in the ObjectList --- src/slic3r/GUI/GUI_ObjectList.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 15c4578d82a..75e384fd965 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2896,7 +2896,8 @@ void ObjectList::update_selections() { const auto item = GetSelection(); if (selection.is_single_full_object()) { - if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject) + if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject && + m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx() ) return; sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } From 8e4b0ac6fd860c06d3436087de3af9d143a666ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sat, 1 May 2021 22:33:59 +0200 Subject: [PATCH 145/154] Added missing include (GCC 11.1) --- src/libslic3r/Optimize/Optimizer.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index 05191eba26c..8ae55c61c58 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace Slic3r { namespace opt { From f191b4611da797667607b85a0afbe20bb2876a36 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 3 May 2021 11:39:53 +0200 Subject: [PATCH 146/154] Further rework of ClipperUtils: Replaced many to_polygons() / to_expolygons() calls with templated ClipperUtils variants to avoid memory allocation and copying. --- src/libslic3r/BridgeDetector.cpp | 20 +- src/libslic3r/Brim.cpp | 2 +- src/libslic3r/ClipperUtils.cpp | 376 +++++++++++--------- src/libslic3r/ClipperUtils.hpp | 144 +++++--- src/libslic3r/ExtrusionEntity.cpp | 4 +- src/libslic3r/Fill/Fill3DHoneycomb.cpp | 2 +- src/libslic3r/Fill/FillAdaptive.cpp | 2 +- src/libslic3r/Fill/FillGyroid.cpp | 2 +- src/libslic3r/Fill/FillHoneycomb.cpp | 2 +- src/libslic3r/Fill/FillPlanePath.cpp | 2 +- src/libslic3r/Layer.cpp | 10 +- src/libslic3r/Layer.hpp | 2 +- src/libslic3r/LayerRegion.cpp | 33 +- src/libslic3r/PerimeterGenerator.cpp | 6 +- src/libslic3r/PrintObject.cpp | 97 +++-- src/libslic3r/SLA/SupportPointGenerator.cpp | 9 +- src/libslic3r/SLA/SupportPointGenerator.hpp | 2 +- src/libslic3r/SupportMaterial.cpp | 53 ++- src/libslic3r/Surface.hpp | 11 +- src/libslic3r/SurfaceCollection.cpp | 37 +- src/libslic3r/SurfaceCollection.hpp | 7 +- tests/libslic3r/test_clipper_utils.cpp | 12 +- 22 files changed, 437 insertions(+), 398 deletions(-) diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index 671ebbdaad0..cd90a1f03d0 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -227,29 +227,33 @@ void ExPolygon::get_trapezoids(ExPolygon clone, Polygons* polygons, double angle // This algorithm may return more trapezoids than necessary // (i.e. it may break a single trapezoid in several because // other parts of the object have x coordinates in the middle) -static void get_trapezoids2(const ExPolygon &expoly, Polygons* polygons) +static void get_trapezoids2(const ExPolygon& expoly, Polygons* polygons) { Polygons src_polygons = to_polygons(expoly); // get all points of this ExPolygon - const Points pp = to_points(src_polygons); - + const Points pp = to_points(src_polygons); + // build our bounding box BoundingBox bb(pp); - + // get all x coordinates std::vector xx; xx.reserve(pp.size()); for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p) xx.push_back(p->x()); std::sort(xx.begin(), xx.end()); - + // find trapezoids by looping from first to next-to-last coordinate + Polygons rectangle; + rectangle.emplace_back(Polygon()); for (std::vector::const_iterator x = xx.begin(); x != xx.end()-1; ++x) { coord_t next_x = *(x + 1); - if (*x != next_x) + if (*x != next_x) { // intersect with rectangle // append results to return value - polygons_append(*polygons, intersection({ { { *x, bb.min.y() }, { next_x, bb.min.y() }, { next_x, bb.max.y() }, { *x, bb.max.y() } } }, src_polygons)); + rectangle.front() = { { *x, bb.min.y() }, { next_x, bb.min.y() }, { next_x, bb.max.y() }, { *x, bb.max.y() } }; + polygons_append(*polygons, intersection(rectangle, src_polygons)); + } } } @@ -302,7 +306,7 @@ Polygons BridgeDetector::coverage(double angle) const covered = union_(covered); // Intersect trapezoids with actual bridge area to remove extra margins and append it to result. polygons_rotate(covered, -(PI/2.0 - angle)); - covered = intersection(covered, to_polygons(this->expolygons)); + covered = intersection(this->expolygons, covered); #if 0 { my @lines = map @{$_->lines}, @$trapezoids; diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 16b81e48829..68851e051e0 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -156,7 +156,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print, const ConstPrint } } - return diff_ex(to_polygons(std::move(brim_area)), no_brim_area); + return diff_ex(brim_area, no_brim_area); } static ExPolygons inner_brim_area(const Print &print, const ConstPrintObjectPtrs &top_level_objects_with_brim, const float no_brim_offset) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 1cd4a7c2ffd..49a24408928 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -188,15 +188,10 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta { return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } #endif // CLIPPERUTILS_UNSAFE_OFFSET -// This is a safe variant of the polygon offset, tailored for a single ExPolygon: -// a single polygon with multiple non-overlapping holes. -// Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours. -ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, - ClipperLib::JoinType joinType, double miterLimit) +// returns number of expolygons collected (0 or 1). +static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) { -// printf("new ExPolygon offset\n"); // 1) Offset the outer contour. - float delta_scaled = delta; ClipperLib::Paths contours; { ClipperLib::ClipperOffset co; @@ -204,153 +199,129 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPath(expolygon.contour.points, joinType, ClipperLib::etClosedPolygon); - co.Execute(contours, delta_scaled); + co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); + co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); + co.Execute(contours, delta); } + if (contours.empty()) + // No need to try to offset the holes. + return 0; - // 2) Offset the holes one by one, collect the results. - ClipperLib::Paths holes; - { - holes.reserve(expolygon.holes.size()); - for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPath(it_hole->points, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out; - // Execute reorients the contours so that the outer most contour has a positive area. Thus the output - // contours will be CCW oriented even though the input paths are CW oriented. - // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. - co.Execute(out, - delta_scaled); - append(holes, std::move(out)); + if (expoly.holes.empty()) { + // No need to subtract holes from the offsetted expolygon, we are done. + append(out, std::move(contours)); + } else { + // 2) Offset the holes one by one, collect the offsetted holes. + ClipperLib::Paths holes; + { + for (const Polygon &hole : expoly.holes) { + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); + co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths out2; + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. + co.Execute(out2, - delta); + append(holes, std::move(out2)); + } + } + + // 3) Subtract holes from the contours. + if (holes.empty()) { + // No hole remaining after an offset. Just copy the outer contour. + append(out, std::move(contours)); + } else if (delta < 0) { + // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. + // Subtract the offsetted holes from the offsetted contours. + ClipperLib::Clipper clipper; + clipper.Clear(); + clipper.AddPaths(contours, ClipperLib::ptSubject, true); + clipper.AddPaths(holes, ClipperLib::ptClip, true); + ClipperLib::Paths output; + clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + if (! output.empty()) { + append(out, std::move(output)); + } else { + // The offsetted holes have eaten up the offsetted outer contour. + return 0; + } + } else { + // Positive offset. As long as the Clipper offset does what one expects it to do, the offsetted hole will have a smaller + // area than the original hole or even disappear, therefore there will be no new intersections. + // Just collect the reversed holes. + out.reserve(contours.size() + holes.size()); + append(out, std::move(contours)); + // Reverse the holes in place. + for (size_t i = 0; i < holes.size(); ++ i) + std::reverse(holes[i].begin(), holes[i].end()); + append(out, std::move(holes)); } } - // 3) Subtract holes from the contours. - ClipperLib::Paths output; - if (holes.empty()) { - output = std::move(contours); - } else { - ClipperLib::Clipper clipper; - clipper.Clear(); - clipper.AddPaths(contours, ClipperLib::ptSubject, true); - clipper.AddPaths(holes, ClipperLib::ptClip, true); - clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - } - - return output; + return 1; +} + +static int offset_expolygon_inner(const Slic3r::Surface &surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) + { return offset_expolygon_inner(surface.expolygon, delta, joinType, miterLimit, out); } +static int offset_expolygon_inner(const Slic3r::Surface *surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) + { return offset_expolygon_inner(surface->expolygon, delta, joinType, miterLimit, out); } + +ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) +{ + ClipperLib::Paths out; + offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out); + return out; } // This is a safe variant of the polygons offset, tailored for multiple ExPolygons. // It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours. // Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united. -ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, - ClipperLib::JoinType joinType, double miterLimit) +template +ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { - float delta_scaled = delta; // Offsetted ExPolygons before they are united. - ClipperLib::Paths contours_cummulative; - contours_cummulative.reserve(expolygons.size()); - // How many non-empty offsetted expolygons were actually collected into contours_cummulative? + ClipperLib::Paths output; + output.reserve(expolygons.size()); + // How many non-empty offsetted expolygons were actually collected into output? // If only one, then there is no need to do a final union. size_t expolygons_collected = 0; - for (const Slic3r::ExPolygon &expoly : expolygons) { - // 1) Offset the outer contour. - ClipperLib::Paths contours; - { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); - co.Execute(contours, delta_scaled); - } - if (contours.empty()) - // No need to try to offset the holes. - continue; - - if (expoly.holes.empty()) { - // No need to subtract holes from the offsetted expolygon, we are done. - append(contours_cummulative, std::move(contours)); - ++ expolygons_collected; - } else { - // 2) Offset the holes one by one, collect the offsetted holes. - ClipperLib::Paths holes; - { - for (const Polygon &hole : expoly.holes) { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); - co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out; - // Execute reorients the contours so that the outer most contour has a positive area. Thus the output - // contours will be CCW oriented even though the input paths are CW oriented. - // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. - co.Execute(out, - delta_scaled); - append(holes, std::move(out)); - } - } - - // 3) Subtract holes from the contours. - if (holes.empty()) { - // No hole remaining after an offset. Just copy the outer contour. - append(contours_cummulative, std::move(contours)); - ++ expolygons_collected; - } else if (delta < 0) { - // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. - // Subtract the offsetted holes from the offsetted contours. - ClipperLib::Clipper clipper; - clipper.Clear(); - clipper.AddPaths(contours, ClipperLib::ptSubject, true); - clipper.AddPaths(holes, ClipperLib::ptClip, true); - ClipperLib::Paths output; - clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - if (! output.empty()) { - append(contours_cummulative, std::move(output)); - ++ expolygons_collected; - } else { - // The offsetted holes have eaten up the offsetted outer contour. - } - } else { - // Positive offset. As long as the Clipper offset does what one expects it to do, the offsetted hole will have a smaller - // area than the original hole or even disappear, therefore there will be no new intersections. - // Just collect the reversed holes. - contours_cummulative.reserve(contours.size() + holes.size()); - append(contours_cummulative, std::move(contours)); - // Reverse the holes in place. - for (size_t i = 0; i < holes.size(); ++ i) - std::reverse(holes[i].begin(), holes[i].end()); - append(contours_cummulative, std::move(holes)); - ++ expolygons_collected; - } - } - } + for (const auto &expoly : expolygons) + expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output); // 4) Unite the offsetted expolygons. - ClipperLib::Paths output; if (expolygons_collected > 1 && delta > 0) { // There is a chance that the outwards offsetted expolygons may intersect. Perform a union. ClipperLib::Clipper clipper; clipper.Clear(); - clipper.AddPaths(contours_cummulative, ClipperLib::ptSubject, true); + clipper.AddPaths(output, ClipperLib::ptSubject, true); clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } else { // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output. - output = std::move(contours_cummulative); } return output; } +Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(_offset(expolygon, delta, joinType, miterLimit)); } +Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(_offset(expolygons, delta, joinType, miterLimit)); } +Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); } +Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); } +Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); } +Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); } +Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return ClipperPaths_to_Slic3rExPolygons(_offset(surfaces, delta, joinType, miterLimit)); } + ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) { // prepare ClipperOffset object @@ -389,16 +360,14 @@ ExPolygons offset2_ex(const Polygons &polygons, const float delta1, const float } //FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper -// conversions and unnecessary Clipper calls. -ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, - const float delta2, ClipperLib::JoinType joinType, double miterLimit) +// conversions and unnecessary Clipper calls. It is not that bad now as Clipper uses Slic3r's own Point / Polygon types directly. +Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { - Polygons polys; - for (const ExPolygon &expoly : expolygons) - append(polys, - offset(offset_ex(expoly, delta1, joinType, miterLimit), - delta2, joinType, miterLimit)); - return union_ex(polys); + return offset(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit); +} +ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + return offset_ex(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit); } template @@ -483,12 +452,22 @@ static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctIntersection, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); } +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool do_safety_offset) { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), do_safety_offset); } Slic3r::Polygons union_(const Slic3r::ExPolygons &subject, bool do_safety_offset) @@ -502,12 +481,45 @@ static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } + Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } +Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool do_safety_offset) { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), do_safety_offset); } Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& subject) @@ -515,67 +527,93 @@ Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& subject) Slic3r::ExPolygons union_ex(const Slic3r::Surfaces& subject) { return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } -Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, const Polygons &clip, bool do_safety_offset) +template +Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip, bool do_safety_offset) { ClipperLib::Clipper clipper; - clipper.AddPaths(ClipperUtils::PolylinesProvider(subject), ClipperLib::ptSubject, false); + clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, false); if (do_safety_offset) - clipper.AddPaths(safety_offset(ClipperUtils::PolygonsProvider(clip)), ClipperLib::ptClip, true); + clipper.AddPaths(safety_offset(std::forward(clip)), ClipperLib::ptClip, true); else - clipper.AddPaths(ClipperUtils::PolygonsProvider(clip), ClipperLib::ptClip, true); + clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); ClipperLib::PolyTree retval; clipper.Execute(clipType, retval, ClipperLib::pftNonZero, ClipperLib::pftNonZero); return PolyTreeToPolylines(std::move(retval)); } -Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool do_safety_offset) +// If the split_at_first_point() call above happens to split the polygon inside the clipping area +// we would get two consecutive polylines instead of a single one, so we go through them in order +// to recombine continuous polylines. +static void _clipper_pl_recombine(Polylines &polylines) { - // transform input polygons into polylines - Polylines polylines; - polylines.reserve(subject.size()); - for (Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) - polylines.emplace_back(polygon->operator Polyline()); // implicit call to split_at_first_point() - - // perform clipping - Polylines retval = _clipper_pl(clipType, polylines, clip, do_safety_offset); - - /* If the split_at_first_point() call above happens to split the polygon inside the clipping area - we would get two consecutive polylines instead of a single one, so we go through them in order - to recombine continuous polylines. */ - for (size_t i = 0; i < retval.size(); ++i) { - for (size_t j = i+1; j < retval.size(); ++j) { - if (retval[i].points.back() == retval[j].points.front()) { + for (size_t i = 0; i < polylines.size(); ++i) { + for (size_t j = i+1; j < polylines.size(); ++j) { + if (polylines[i].points.back() == polylines[j].points.front()) { /* If last point of i coincides with first point of j, append points of j to i and delete j */ - retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); - retval.erase(retval.begin() + j); + polylines[i].points.insert(polylines[i].points.end(), polylines[j].points.begin()+1, polylines[j].points.end()); + polylines.erase(polylines.begin() + j); --j; - } else if (retval[i].points.front() == retval[j].points.back()) { + } else if (polylines[i].points.front() == polylines[j].points.back()) { /* If first point of i coincides with last point of j, prepend points of j to i and delete j */ - retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); - retval.erase(retval.begin() + j); + polylines[i].points.insert(polylines[i].points.begin(), polylines[j].points.begin(), polylines[j].points.end()-1); + polylines.erase(polylines.begin() + j); --j; - } else if (retval[i].points.front() == retval[j].points.front()) { + } else if (polylines[i].points.front() == polylines[j].points.front()) { /* Since Clipper does not preserve orientation of polylines, also check the case when first point of i coincides with first point of j. */ - retval[j].reverse(); - retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); - retval.erase(retval.begin() + j); + polylines[j].reverse(); + polylines[i].points.insert(polylines[i].points.begin(), polylines[j].points.begin(), polylines[j].points.end()-1); + polylines.erase(polylines.begin() + j); --j; - } else if (retval[i].points.back() == retval[j].points.back()) { + } else if (polylines[i].points.back() == polylines[j].points.back()) { /* Since Clipper does not preserve orientation of polylines, also check the case when last point of i coincides with last point of j. */ - retval[j].reverse(); - retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); - retval.erase(retval.begin() + j); + polylines[j].reverse(); + polylines[i].points.insert(polylines[i].points.end(), polylines[j].points.begin()+1, polylines[j].points.end()); + polylines.erase(polylines.begin() + j); --j; } } } +} + +template +Polylines _clipper_pl_closed(ClipperLib::ClipType clipType, PathProvider1 &&subject, PathProvider2 &&clip, bool do_safety_offset) +{ + // Transform input polygons into open paths. + ClipperLib::Paths paths; + paths.reserve(subject.size()); + for (const Points &poly : subject) { + // Emplace polygon, duplicate the 1st point. + paths.push_back({}); + ClipperLib::Path &path = paths.back(); + path.reserve(poly.size() + 1); + path = poly; + path.emplace_back(poly.front()); + } + // perform clipping + Polylines retval = _clipper_pl_open(clipType, paths, std::forward(clip), do_safety_offset); + _clipper_pl_recombine(retval); return retval; } +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip, bool do_safety_offset) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); } +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) + { return _clipper_pl_closed(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } + Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, bool do_safety_offset) { // convert Lines to Polylines @@ -585,7 +623,7 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol polylines.emplace_back(Polyline(line.a, line.b)); // perform operation - polylines = _clipper_pl(clipType, polylines, clip, do_safety_offset); + polylines = _clipper_pl_open(clipType, ClipperUtils::PolylinesProvider(polylines), ClipperUtils::PolygonsProvider(clip), do_safety_offset); // convert Polylines to Lines Lines retval; diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index d7eb371275d..0d3b986c061 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -212,6 +212,47 @@ namespace ClipperUtils { const Surfaces &m_surfaces; size_t m_size; }; + + struct SurfacesPtrProvider { + SurfacesPtrProvider(const SurfacesPtr &surfaces) : m_surfaces(surfaces) { + m_size = 0; + for (const Surface *surface : surfaces) + m_size += surface->expolygon.holes.size() + 1; + } + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(SurfacesPtr::const_iterator it) : m_it_surface(it), m_idx_contour(0) {} + const Points& operator*() const { return (m_idx_contour == 0) ? (*m_it_surface)->expolygon.contour.points : (*m_it_surface)->expolygon.holes[m_idx_contour - 1].points; } + bool operator==(const iterator &rhs) const { return m_it_surface == rhs.m_it_surface && m_idx_contour == rhs.m_idx_contour; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + iterator& operator++() { + if (++ m_idx_contour == (*m_it_surface)->expolygon.holes.size() + 1) { + ++ m_it_surface; + m_idx_contour = 0; + } + return *this; + } + const Points& operator++(int) { + const Points &out = **this; + ++ (*this); + return out; + } + private: + SurfacesPtr::const_iterator m_it_surface; + int m_idx_contour; + }; + + iterator cbegin() const { return iterator(m_surfaces.cbegin()); } + iterator begin() const { return this->cbegin(); } + iterator cend() const { return iterator(m_surfaces.cend()); } + iterator end() const { return this->cend(); } + size_t size() const { return m_size; } + + private: + const SurfacesPtr &m_surfaces; + size_t m_size; + }; } ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); @@ -224,80 +265,71 @@ Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, Cli #endif // CLIPPERUTILS_UNSAFE_OFFSET // offset Polylines -Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -// offset expolygons and surfaces -ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit); -ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit); -inline Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return to_polygons(_offset(expolygon, delta, joinType, miterLimit)); } -inline Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return to_polygons(_offset(expolygons, delta, joinType, miterLimit)); } +Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); #ifdef CLIPPERUTILS_UNSAFE_OFFSET Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); #endif // CLIPPERUTILS_UNSAFE_OFFSET -inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); } -inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) - { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); } - -#ifdef CLIPPERUTILS_UNSAFE_OFFSET -ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -#endif // CLIPPERUTILS_UNSAFE_OFFSET - -Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, - const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); - -Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygon &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); - -inline Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) -{ - return _clipper_pl(ClipperLib::ctDifference, subject, clip, do_safety_offset); -} - -inline Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) -{ - return _clipper_pl(ClipperLib::ctDifference, subject, clip, do_safety_offset); -} +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, bool do_safety_offset = false); +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip, bool do_safety_offset = false); +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { return _clipper_ln(ClipperLib::ctDifference, subject, clip, do_safety_offset); } -Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, bool do_safety_offset = false); +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); - -inline Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) -{ - return _clipper_pl(ClipperLib::ctIntersection, subject, clip, do_safety_offset); -} - -inline Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) -{ - return _clipper_pl(ClipperLib::ctIntersection, subject, clip, do_safety_offset); -} +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, bool do_safety_offset = false); +Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); +Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) { diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 3284bc39e4a..714a122a01f 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -14,12 +14,12 @@ namespace Slic3r { void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(intersection_pl((Polylines)polyline, to_polygons(collection.expolygons)), retval); + this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection.expolygons), retval); } void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(diff_pl((Polylines)this->polyline, to_polygons(collection.expolygons)), retval); + this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection.expolygons), retval); } void ExtrusionPath::clip_end(double distance) diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 95c26fbad42..0dec8004b2d 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -162,7 +162,7 @@ void Fill3DHoneycomb::_fill_surface_single( pl.translate(bb.min); // clip pattern to boundaries, chain the clipped polylines - polylines = intersection_pl(polylines, to_polygons(expolygon)); + polylines = intersection_pl(polylines, expolygon); // connect lines if needed if (params.dont_connect() || polylines.size() <= 1) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index d8c05887ece..6b303e636ab 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -1368,7 +1368,7 @@ void Filler::_fill_surface_single( all_polylines.reserve(lines.size()); std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); // Crop all polylines - all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon)); + all_polylines = intersection_pl(std::move(all_polylines), expolygon); #endif } diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index ff2d049cfdd..c6510daa2ec 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -180,7 +180,7 @@ void FillGyroid::_fill_surface_single( for (Polyline &pl : polylines) pl.translate(bb.min); - polylines = intersection_pl(polylines, to_polygons(expolygon)); + polylines = intersection_pl(polylines, expolygon); if (! polylines.empty()) { // Remove very small bits, but be careful to not remove infill lines connecting thin walls! diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index f7f79ae833b..5dc2ed501c4 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -73,7 +73,7 @@ void FillHoneycomb::_fill_surface_single( } } - all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon)); + all_polylines = intersection_pl(std::move(all_polylines), expolygon); if (params.dont_connect() || all_polylines.size() <= 1) append(polylines_out, chain_polylines(std::move(all_polylines))); else diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 7beaf2f08e9..6385a880e96 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -44,7 +44,7 @@ void FillPlanePath::_fill_surface_single( coord_t(floor(pt.x() * distance_between_lines + 0.5)), coord_t(floor(pt.y() * distance_between_lines + 0.5)))); // intersection(polylines_src, offset((Polygons)expolygon, scale_(0.02)), &polylines); - polylines = intersection_pl(std::move(polylines), to_polygons(expolygon)); + polylines = intersection_pl(std::move(polylines), expolygon); Polylines chained; if (params.dont_connect() || params.density > 0.5 || polylines.size() <= 1) chained = chain_polylines(std::move(polylines)); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 857c98f52e8..e8e3c4275c2 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -39,11 +39,11 @@ void Layer::make_slices() ExPolygons slices; if (m_regions.size() == 1) { // optimization: if we only have one region, take its slices - slices = m_regions.front()->slices; + slices = to_expolygons(m_regions.front()->slices.surfaces); } else { Polygons slices_p; for (LayerRegion *layerm : m_regions) - polygons_append(slices_p, to_polygons(layerm->slices)); + polygons_append(slices_p, to_polygons(layerm->slices.surfaces)); slices = union_ex(slices_p); } @@ -105,7 +105,7 @@ ExPolygons Layer::merged(float offset_scaled) const const PrintRegionConfig &config = layerm->region()->config(); // Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty. if (config.bottom_solid_layers > 0 || config.top_solid_layers > 0 || config.fill_density > 0. || config.perimeters > 0) - append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled)); + append(polygons, offset(layerm->slices.surfaces, offset_scaled)); } ExPolygons out = union_ex(polygons); if (offset_scaled2 != 0.f) @@ -185,7 +185,7 @@ void Layer::make_perimeters() } // merge the surfaces assigned to each group for (std::pair &surfaces_with_extra_perimeters : slices) - new_slices.append(offset_ex(to_expolygons(surfaces_with_extra_perimeters.second), ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); + new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); } // make perimeters @@ -196,7 +196,7 @@ void Layer::make_perimeters() if (!fill_surfaces.surfaces.empty()) { for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) { // Separate the fill surfaces. - ExPolygons expp = intersection_ex(to_polygons(fill_surfaces), (*l)->slices); + ExPolygons expp = intersection_ex(fill_surfaces.surfaces, (*l)->slices.surfaces); (*l)->fill_expolygons = expp; (*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front()); } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 87296f8f1cb..2e3e29eab87 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -196,7 +196,7 @@ protected: // between the raft and the object first layer. SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : Layer(id, object, height, print_z, slice_z) {} - virtual ~SupportLayer() {} + virtual ~SupportLayer() = default; }; } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 059d94e25be..5b4a021b02c 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -49,19 +49,17 @@ void LayerRegion::slices_to_fill_surfaces_clipped() // in place. However we're now only using its boundaries (which are invariant) // so we're safe. This guarantees idempotence of prepare_infill() also in case // that combine_infill() turns some fill_surface into VOID surfaces. -// Polygons fill_boundaries = to_polygons(std::move(this->fill_surfaces)); - Polygons fill_boundaries = to_polygons(this->fill_expolygons); // Collect polygons per surface type. - std::vector polygons_by_surface; - polygons_by_surface.assign(size_t(stCount), Polygons()); + std::vector by_surface; + by_surface.assign(size_t(stCount), SurfacesPtr()); for (Surface &surface : this->slices.surfaces) - polygons_append(polygons_by_surface[(size_t)surface.surface_type], surface.expolygon); + by_surface[size_t(surface.surface_type)].emplace_back(&surface); // Trim surfaces by the fill_boundaries. this->fill_surfaces.surfaces.clear(); for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) { - const Polygons &polygons = polygons_by_surface[surface_type]; - if (! polygons.empty()) - this->fill_surfaces.append(intersection_ex(polygons, fill_boundaries), SurfaceType(surface_type)); + const SurfacesPtr &this_surfaces = by_surface[surface_type]; + if (! this_surfaces.empty()) + this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type)); } } @@ -221,7 +219,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; } else { // Found an island, to which this bridge region belongs. Trim it, - polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island])); + polys = intersection(polys, fill_boundaries_ex[idx_island]); } bridge_bboxes.push_back(get_extents(polys)); bridges_grown.push_back(std::move(polys)); @@ -325,11 +323,11 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly if (s1.empty()) continue; Polygons polys; - polygons_append(polys, std::move(s1)); + polygons_append(polys, to_polygons(std::move(s1))); for (size_t j = i + 1; j < top.size(); ++ j) { Surface &s2 = top[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { - polygons_append(polys, std::move(s2)); + polygons_append(polys, to_polygons(std::move(s2))); s2.clear(); } } @@ -351,11 +349,11 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly if (s1.empty()) continue; Polygons polys; - polygons_append(polys, std::move(s1)); + polygons_append(polys, to_polygons(std::move(s1))); for (size_t j = i + 1; j < internal.size(); ++ j) { Surface &s2 = internal[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { - polygons_append(polys, std::move(s2)); + polygons_append(polys, to_polygons(std::move(s2))); s2.clear(); } } @@ -423,7 +421,7 @@ void LayerRegion::trim_surfaces(const Polygons &trimming_polygons) for (const Surface &surface : this->slices.surfaces) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - this->slices.set(intersection_ex(to_polygons(std::move(this->slices.surfaces)), trimming_polygons, false), stInternal); + this->slices.set(intersection_ex(this->slices.surfaces, trimming_polygons), stInternal); } void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons) @@ -432,10 +430,9 @@ void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_comp for (const Surface &surface : this->slices.surfaces) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - ExPolygons slices_expolygons = to_expolygons(std::move(this->slices.surfaces)); - Polygons slices_polygons = to_polygons(slices_expolygons); - Polygons tmp = intersection(slices_polygons, trimming_polygons, false); - append(tmp, diff(slices_polygons, offset(offset_ex(slices_expolygons, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step))); + ExPolygons surfaces = to_expolygons(std::move(this->slices.surfaces)); + Polygons tmp = intersection(surfaces, trimming_polygons); + append(tmp, diff(surfaces, offset(offset_ex(surfaces, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step))); this->slices.set(union_ex(tmp), stInternal); } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 6ec4dbf6b82..a459b90fad6 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -349,9 +349,7 @@ void PerimeterGenerator::process() coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); ExPolygons expp = offset2_ex( // medial axis requires non-overlapping geometry - diff_ex(to_polygons(last), - offset(offsets, float(ext_perimeter_width / 2.)), - true), + diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.)), true), - float(min_width / 2.), float(min_width / 2.)); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop for (ExPolygon &ex : expp) @@ -514,7 +512,7 @@ void PerimeterGenerator::process() and use zigzag). */ //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, // therefore it may cover the area, but no the volume. - last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f)); + last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f)); this->gap_fill->append(std::move(gap_fill.entities)); } } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index cbf3e71ab73..d0a6a871f87 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -186,7 +186,7 @@ void PrintObject::make_perimeters() m_print->throw_if_canceled(); LayerRegion &layerm = *m_layers[layer_idx]->m_regions[region_id]; const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->m_regions[region_id]; - const Polygons upper_layerm_polygons = upper_layerm.slices; + const Polygons upper_layerm_polygons = to_polygons(upper_layerm.slices.surfaces); // Filter upper layer polygons in intersection_ppl by their bounding boxes? // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; const double total_loop_length = total_length(upper_layerm_polygons); @@ -809,19 +809,14 @@ void PrintObject::detect_surfaces_type() // collapse very narrow parts (using the safety offset in the diff is not enough) float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f; - Polygons layerm_slices_surfaces = to_polygons(layerm->slices.surfaces); - // find top surfaces (difference between current surfaces // of current layer and upper one) Surfaces top; if (upper_layer) { - Polygons upper_slices = interface_shells ? - to_polygons(upper_layer->m_regions[idx_region]->slices.surfaces) : - to_polygons(upper_layer->lslices); - surfaces_append(top, - //FIXME implement offset2_ex working over ExPolygons, that should be a bit more efficient than calling offset_ex twice. - offset_ex(offset_ex(diff_ex(layerm_slices_surfaces, upper_slices, true), -offset), offset), - stTop); + ExPolygons upper_slices = interface_shells ? + diff_ex(layerm->slices.surfaces, upper_layer->m_regions[idx_region]->slices.surfaces, true) : + diff_ex(layerm->slices.surfaces, upper_layer->lslices, true); + surfaces_append(top, offset2_ex(upper_slices, -offset, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection @@ -839,14 +834,14 @@ void PrintObject::detect_surfaces_type() to_polygons(lower_layer->get_region(idx_region)->slices.surfaces) : to_polygons(lower_layer->slices); surfaces_append(bottom, - offset2_ex(diff(layerm_slices_surfaces, lower_slices, true), -offset, offset), + offset2_ex(diff(layerm->slices.surfaces, lower_slices, true), -offset, offset), surface_type_bottom_other); #else // Any surface lying on the void is a true bottom bridge (an overhang) surfaces_append( bottom, offset2_ex( - diff(layerm_slices_surfaces, to_polygons(lower_layer->lslices), true), + diff_ex(layerm->slices.surfaces, lower_layer->lslices, true), -offset, offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -857,10 +852,10 @@ void PrintObject::detect_surfaces_type() surfaces_append( bottom, offset2_ex( - diff( - intersection(layerm_slices_surfaces, to_polygons(lower_layer->lslices)), // supported - to_polygons(lower_layer->m_regions[idx_region]->slices.surfaces), - true), + diff_ex( + intersection(layerm->slices.surfaces, lower_layer->lslices), // supported + lower_layer->m_regions[idx_region]->slices.surfaces, + true), -offset, offset), stBottom); } @@ -883,7 +878,7 @@ void PrintObject::detect_surfaces_type() Polygons top_polygons = to_polygons(std::move(top)); top.clear(); surfaces_append(top, - diff_ex(top_polygons, to_polygons(bottom), false), + diff_ex(top_polygons, bottom, false), stTop); } @@ -900,15 +895,18 @@ void PrintObject::detect_surfaces_type() // save surfaces to layer Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->slices.surfaces; - surfaces_out.clear(); + Surfaces surfaces_backup; + if (! interface_shells) { + surfaces_backup = std::move(surfaces_out); + surfaces_out.clear(); + } + const Surfaces &surfaces_prev = interface_shells ? layerm->slices.surfaces : surfaces_backup; // find internal surfaces (difference between top/bottom surfaces and others) { Polygons topbottom = to_polygons(top); polygons_append(topbottom, to_polygons(bottom)); - surfaces_append(surfaces_out, - diff_ex(layerm_slices_surfaces, topbottom, false), - stInternal); + surfaces_append(surfaces_out, diff_ex(surfaces_prev, topbottom, false), stInternal); } surfaces_append(surfaces_out, std::move(top)); @@ -1012,7 +1010,7 @@ void PrintObject::process_external_surfaces() // Shrink the holes, let the layer above expand slightly inside the unsupported areas. polygons_append(voids, offset(surface.expolygon, unsupported_width)); } - surfaces_covered[layer_idx] = diff(to_polygons(this->m_layers[layer_idx]->lslices), voids); + surfaces_covered[layer_idx] = diff(this->m_layers[layer_idx]->lslices, voids); } } ); @@ -1107,11 +1105,11 @@ void PrintObject::discover_vertical_shells() LayerRegion &layerm = *layer.m_regions[idx_region]; float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. - append(cache.top_surfaces, offset(to_expolygons(layerm.slices.filter_by_type(stTop)), min_perimeter_infill_spacing)); - append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stTop)), min_perimeter_infill_spacing)); + append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing)); + append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing)); // Bottom surfaces. - append(cache.bottom_surfaces, offset(to_expolygons(layerm.slices.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); - append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; @@ -1181,11 +1179,11 @@ void PrintObject::discover_vertical_shells() float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. auto &cache = cache_top_botom_regions[idx_layer]; - cache.top_surfaces = offset(to_expolygons(layerm.slices.filter_by_type(stTop)), min_perimeter_infill_spacing); - append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stTop)), min_perimeter_infill_spacing)); + cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing); + append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing)); // Bottom surfaces. - cache.bottom_surfaces = offset(to_expolygons(layerm.slices.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing); - append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); + cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); // Holes over all regions. Only collect them once, they are valid for all idx_region iterations. if (cache.holes.empty()) { for (size_t idx_region = 0; idx_region < layer.regions().size(); ++ idx_region) @@ -1407,16 +1405,8 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. - Slic3r::ExPolygons new_internal = diff_ex( - to_polygons(layerm->fill_surfaces.filter_by_type(stInternal)), - shell, - false - ); - Slic3r::ExPolygons new_internal_void = diff_ex( - to_polygons(layerm->fill_surfaces.filter_by_type(stInternalVoid)), - shell, - false - ); + Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), shell); + Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1521,8 +1511,8 @@ void PrintObject::bridge_over_infill() #endif // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_polygons(to_bridge), true); - to_bridge = intersection_ex(to_polygons(to_bridge), internal_solid, true); + ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, true); + to_bridge = intersection_ex(to_bridge, internal_solid, true); // build the new collection of fill_surfaces layerm->fill_surfaces.remove_type(stInternalSolid); for (ExPolygon &ex : to_bridge) @@ -1875,7 +1865,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) slices = offset_ex(std::move(slices), delta); if (! processed.empty()) // Trim by the slices of already processed regions. - slices = diff_ex(to_polygons(std::move(slices)), processed); + slices = diff_ex(slices, processed); if (size_t(&sliced_volume - &sliced_volumes.front()) + 1 < sliced_volumes.size()) // Collect the already processed regions to trim the to be processed regions. polygons_append(processed, slices); @@ -1926,12 +1916,11 @@ void PrintObject::_slice(const std::vector &layer_height_profile) LayerRegion *other_layerm = layer->m_regions[other_region_id]; if (layerm == nullptr || other_layerm == nullptr || other_layerm->slices.empty() || expolygons_by_layer[layer_id].empty()) continue; - Polygons other_slices = to_polygons(other_layerm->slices); - ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id])); + ExPolygons my_parts = intersection_ex(other_layerm->slices.surfaces, expolygons_by_layer[layer_id]); if (my_parts.empty()) continue; // Remove such parts from original region. - other_layerm->slices.set(diff_ex(other_slices, to_polygons(my_parts)), stInternal); + other_layerm->slices.set(diff_ex(other_layerm->slices.surfaces, my_parts), stInternal); // Append new parts to our region. layerm->slices.append(std::move(my_parts), stInternal); } @@ -2018,7 +2007,7 @@ end: slices = offset_ex(std::move(slices), xy_compensation_scaled); if (region_id > 0 && clip) // Trim by the slices of already processed regions. - slices = diff_ex(to_polygons(std::move(slices)), processed); + slices = diff_ex(slices, processed); if (clip && (region_id + 1 < layer->m_regions.size())) // Collect the already processed regions to trim the to be processed regions. polygons_append(processed, slices); @@ -2649,10 +2638,7 @@ void PrintObject::discover_horizontal_shells() neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid); // subtract intersections from layer surfaces to get resulting internal surfaces Polygons polygons_internal = to_polygons(std::move(internal_solid)); - ExPolygons internal = diff_ex( - to_polygons(backup.filter_by_type(stInternal)), - polygons_internal, - true); + ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, true); // assign resulting internal surfaces to layer neighbor_layerm->fill_surfaces.append(internal, stInternal); polygons_append(polygons_internal, to_polygons(std::move(internal))); @@ -2663,7 +2649,7 @@ void PrintObject::discover_horizontal_shells() backup.group(&top_bottom_groups); for (SurfacesPtr &group : top_bottom_groups) neighbor_layerm->fill_surfaces.append( - diff_ex(to_polygons(group), polygons_internal), + diff_ex(group, polygons_internal), // Use an existing surface as a template, it carries the bridge angle etc. *group.front()); } @@ -2742,10 +2728,7 @@ void PrintObject::combine_infill() ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal)); // Start looping from the second layer and intersect the current intersection with it. for (size_t i = 1; i < layerms.size(); ++ i) - intersection = intersection_ex( - to_polygons(intersection), - to_polygons(layerms[i]->fill_surfaces.filter_by_type(stInternal)), - false); + intersection = intersection_ex(layerms[i]->fill_surfaces.filter_by_type(stInternal), intersection); double area_threshold = layerms.front()->infill_area_threshold(); if (! intersection.empty() && area_threshold > 0.) intersection.erase(std::remove_if(intersection.begin(), intersection.end(), @@ -2774,7 +2757,7 @@ void PrintObject::combine_infill() for (ExPolygon &expoly : intersection) polygons_append(intersection_with_clearance, offset(expoly, clearance_offset)); for (LayerRegion *layerm : layerms) { - Polygons internal = to_polygons(layerm->fill_surfaces.filter_by_type(stInternal)); + Polygons internal = to_polygons(std::move(layerm->fill_surfaces.filter_by_type(stInternal))); layerm->fill_surfaces.remove_type(stInternal); layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance, false), stInternal); if (layerm == layerms.back()) { diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 5ef4eb00163..bbc6b03fa59 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -179,9 +179,8 @@ static std::vector make_layers( } } if (! top.islands_below.empty()) { - Polygons top_polygons = to_polygons(*top.polygon); Polygons bottom_polygons = top.polygons_below(); - top.overhangs = diff_ex(top_polygons, bottom_polygons); + top.overhangs = diff_ex(*top.polygon, bottom_polygons); if (! top.overhangs.empty()) { // Produce 2 bands around the island, a safe band for dangling overhangs @@ -191,7 +190,7 @@ static std::vector make_layers( auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare); // Absolutely hopeless overhangs are those outside the unsafe band - top.overhangs = diff_ex(top_polygons, overh_mask); + top.overhangs = diff_ex(*top.polygon, overh_mask); // Now cut out the supported core from the safe band // and cut the safe band from the unsafe band to get distinct @@ -199,8 +198,8 @@ static std::vector make_layers( overh_mask = diff(overh_mask, dangl_mask); dangl_mask = diff(dangl_mask, bottom_polygons); - top.dangling_areas = intersection_ex(top_polygons, dangl_mask); - top.overhangs_slopes = intersection_ex(top_polygons, overh_mask); + top.dangling_areas = intersection_ex(*top.polygon, dangl_mask); + top.overhangs_slopes = intersection_ex(*top.polygon, overh_mask); top.overhangs_area = 0.f; std::vector> expolys_with_areas; diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index d7588e3ba3c..9ceda7896b6 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -90,7 +90,7 @@ public: float overlap_area(const Structure &rhs) const { double out = 0.; if (this->bbox.overlap(rhs.bbox)) { - Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false); + Polygons polys = intersection(*this->polygon, *rhs.polygon, false); for (const Polygon &poly : polys) out += poly.area(); } diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 08cd04b909c..b4f5fefae29 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -815,7 +815,7 @@ public: // Expanding, thus m_support_polygons are all inside islands. union_ex(*m_support_polygons) : // Shrinking, thus m_support_polygons may be trimmed a tiny bit by islands. - intersection_ex(*m_support_polygons, to_polygons(islands))); + intersection_ex(*m_support_polygons, islands)); std::vector> samples_inside; for (ExPolygon &island : islands) { @@ -932,7 +932,7 @@ public: } // Deserialization constructor - bool deserialize_(const std::string &path, int which = -1) + bool deserialize_(const std::string &path, int which = -1) { FILE *file = ::fopen(path.c_str(), "rb"); if (file == nullptr) @@ -961,7 +961,7 @@ public: poly.points.emplace_back(Point(x * scale, y * scale)); } if (which == -1 || which == i) - m_support_polygons_deserialized.emplace_back(std::move(poly)); + m_support_polygons_deserialized.emplace_back(std::move(poly)); printf("Polygon %d, area: %lf\n", i, area(poly.points)); } ::fread(&n_polygons, 4, 1, file); @@ -984,14 +984,14 @@ public: m_support_polygons_deserialized = simplify_polygons(m_support_polygons_deserialized, false); //m_support_polygons_deserialized = to_polygons(union_ex(m_support_polygons_deserialized, false)); - // Create an EdgeGrid, initialize it with projection, initialize signed distance field. - coord_t grid_resolution = coord_t(scale_(m_support_spacing)); - BoundingBox bbox = get_extents(*m_support_polygons); + // Create an EdgeGrid, initialize it with projection, initialize signed distance field. + coord_t grid_resolution = coord_t(scale_(m_support_spacing)); + BoundingBox bbox = get_extents(*m_support_polygons); bbox.offset(20); - bbox.align_to_grid(grid_resolution); - m_grid.set_bbox(bbox); - m_grid.create(*m_support_polygons, grid_resolution); - m_grid.calculate_sdf(); + bbox.align_to_grid(grid_resolution); + m_grid.set_bbox(bbox); + m_grid.create(*m_support_polygons, grid_resolution); + m_grid.calculate_sdf(); return true; } @@ -1285,7 +1285,7 @@ namespace SupportMaterialInternal { // Is the straight perimeter segment supported at both sides? Point pts[2] = { polyline.first_point(), polyline.last_point() }; bool supported[2] = { false, false }; - for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) + for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) for (int j = 0; j < 2; ++ j) if (! supported[j] && lower_layer.lslices_bboxes[i].contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) supported[j] = true; @@ -1437,7 +1437,7 @@ static inline std::tuple detect_overhangs( 0.5f * fw); // Overhang polygons for this layer and region. Polygons diff_polygons; - Polygons layerm_polygons = to_polygons(layerm->slices); + Polygons layerm_polygons = to_polygons(layerm->slices.surfaces); if (lower_layer_offset == 0.f) { // Support everything. diff_polygons = diff(layerm_polygons, lower_layer_polygons); @@ -1469,13 +1469,13 @@ static inline std::tuple detect_overhangs( diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); } if (! diff_polygons.empty()) { - // Offset the support regions back to a full overhang, restrict them to the full overhang. - // This is done to increase size of the supporting columns below, as they are calculated by - // propagating these contact surfaces downwards. - diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); - } + // Offset the support regions back to a full overhang, restrict them to the full overhang. + // This is done to increase size of the supporting columns below, as they are calculated by + // propagating these contact surfaces downwards. + diff_polygons = diff( + intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); + } } } @@ -1489,7 +1489,7 @@ static inline std::tuple detect_overhangs( // Subtracting them as they are may leave unwanted narrow // residues of diff_polygons that would then be supported. diff_polygons = diff(diff_polygons, - offset(union_(to_polygons(std::move(annotations.blockers_layers[layer_id]))), float(1000.*SCALED_EPSILON))); + offset(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); } #ifdef SLIC3R_DEBUG @@ -1538,7 +1538,7 @@ static inline std::tuple detect_overhangs( slices_margin.offset = slices_margin_offset; slices_margin.polygons = (slices_margin_offset == 0.f) ? lower_layer_polygons : - offset2(to_polygons(lower_layer.lslices), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); + offset2(lower_layer.lslices, - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { if (has_enforcer) // Make a backup of trimming polygons before enforcing "on build plate only". @@ -1569,9 +1569,9 @@ static inline std::tuple detect_overhangs( if (has_enforcer) { // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. #ifdef SLIC3R_DEBUG - ExPolygons enforcers_united = union_ex(to_polygons(annotations.enforcers_layers[layer_id]), false); + ExPolygons enforcers_united = union_ex(annotations.enforcers_layers[layer_id]); #endif // SLIC3R_DEBUG - enforcer_polygons = diff(intersection(to_polygons(layer.lslices), to_polygons(std::move(annotations.enforcers_layers[layer_id]))), + enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]), // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #ifdef SLIC3R_DEBUG @@ -2772,8 +2772,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( break; some_region_overlaps = true; polygons_append(polygons_trimming, - offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)), - gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(region->fill_surfaces.filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (region->region()->config().overhangs.value) // Add bridging perimeters. SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); @@ -3093,8 +3092,8 @@ static inline void fill_expolygon_generate_paths( Polylines polylines; try { polylines = filler->fill_surface(&surface, fill_params); - } catch (InfillFailedException &) { - } + } catch (InfillFailedException &) { + } extrusion_entities_append_paths( dst, std::move(polylines), diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index fbebe561023..4920efbbfdb 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -90,7 +90,6 @@ public: return *this; } - operator Polygons() const { return this->expolygon; } double area() const { return this->expolygon.area(); } bool empty() const { return expolygon.empty(); } void clear() { expolygon.clear(); } @@ -107,6 +106,16 @@ public: typedef std::vector Surfaces; typedef std::vector SurfacesPtr; +inline Polygons to_polygons(const Surface &surface) +{ + return to_polygons(surface.expolygon); +} + +inline Polygons to_polygons(Surface &&surface) +{ + return to_polygons(std::move(surface.expolygon)); +} + inline Polygons to_polygons(const Surfaces &src) { size_t num = 0; diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index 6db59930679..ec847d2a3fd 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -6,18 +6,7 @@ namespace Slic3r { -SurfaceCollection::operator Polygons() const -{ - return to_polygons(surfaces); -} - -SurfaceCollection::operator ExPolygons() const -{ - return to_expolygons(surfaces); -} - -void -SurfaceCollection::simplify(double tolerance) +void SurfaceCollection::simplify(double tolerance) { Surfaces ss; for (Surfaces::const_iterator it_s = this->surfaces.begin(); it_s != this->surfaces.end(); ++it_s) { @@ -33,8 +22,7 @@ SurfaceCollection::simplify(double tolerance) } /* group surfaces by common properties */ -void -SurfaceCollection::group(std::vector *retval) +void SurfaceCollection::group(std::vector *retval) { for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { // find a group with the same properties @@ -54,8 +42,7 @@ SurfaceCollection::group(std::vector *retval) } } -SurfacesPtr -SurfaceCollection::filter_by_type(const SurfaceType type) +SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) { SurfacesPtr ss; for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { @@ -64,8 +51,7 @@ SurfaceCollection::filter_by_type(const SurfaceType type) return ss; } -SurfacesPtr -SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) +SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) { SurfacesPtr ss; for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { @@ -79,8 +65,7 @@ SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) return ss; } -void -SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) +void SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) { for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { if (surface->surface_type == type) { @@ -90,8 +75,7 @@ SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) } } -void -SurfaceCollection::keep_type(const SurfaceType type) +void SurfaceCollection::keep_type(const SurfaceType type) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { @@ -105,8 +89,7 @@ SurfaceCollection::keep_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void -SurfaceCollection::keep_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { @@ -127,8 +110,7 @@ SurfaceCollection::keep_types(const SurfaceType *types, int ntypes) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void -SurfaceCollection::remove_type(const SurfaceType type) +void SurfaceCollection::remove_type(const SurfaceType type) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { @@ -142,8 +124,7 @@ SurfaceCollection::remove_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void -SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 9f0324d20a5..7e01a68dfbb 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -12,11 +12,10 @@ class SurfaceCollection public: Surfaces surfaces; - SurfaceCollection() {}; - SurfaceCollection(const Surfaces &surfaces) : surfaces(surfaces) {}; + SurfaceCollection() = default; + SurfaceCollection(const Surfaces& surfaces) : surfaces(surfaces) {}; SurfaceCollection(Surfaces &&surfaces) : surfaces(std::move(surfaces)) {}; - operator Polygons() const; - operator ExPolygons() const; + void simplify(double tolerance); void group(std::vector *retval); template bool any_internal_contains(const T &item) const { diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index a660b29cb2a..bbf76ea1806 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -22,7 +22,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { THEN("offset matches") { REQUIRE(result == Polygons { { { 205, 205 }, { 95, 205 }, { 95, 95 }, { 205, 95 }, }, - { { 145, 145 }, { 145, 155 }, { 155, 155 }, { 155, 145 } } }); + { { 155, 145 }, { 145, 145 }, { 145, 155 }, { 155, 155 } } }); } } WHEN("offset_ex") { @@ -56,7 +56,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { } GIVEN("square and hole") { WHEN("diff_ex") { - ExPolygons result = Slic3r::diff_ex({ square }, { hole_in_square }); + ExPolygons result = Slic3r::diff_ex(Polygons{ square }, Polygons{ hole_in_square }); THEN("hole is created") { REQUIRE(result.size() == 1); REQUIRE(square_with_hole.area() == result.front().area()); @@ -77,7 +77,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { } } WHEN("diff_pl") { - Polylines result = Slic3r::diff_pl({ polyline }, { square, hole_in_square }); + Polylines result = Slic3r::diff_pl({ polyline }, Polygons{ square, hole_in_square }); THEN("correct number of result lines") { REQUIRE(result.size() == 3); } @@ -180,7 +180,7 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { // CW oriented contour Slic3r::Polygon hole_in_square { { 14, 14 }, { 14, 16 }, { 16, 16 }, { 16, 14 } }; WHEN("intersection_ex with another square") { - ExPolygons intersection = Slic3r::intersection_ex({ square, hole_in_square }, { square2 }); + ExPolygons intersection = Slic3r::intersection_ex(Polygons{ square, hole_in_square }, Polygons{ square2 }); THEN("intersection area matches (hole is preserved)") { ExPolygon match({ { 20, 18 }, { 10, 18 }, { 10, 12 }, { 20, 12 } }, { { 14, 16 }, { 16, 16 }, { 16, 14 }, { 14, 14 } }); @@ -203,7 +203,7 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { } } WHEN("diff_ex with another square") { - ExPolygons diff = Slic3r::diff_ex({ square, square2 }, { hole }); + ExPolygons diff = Slic3r::diff_ex(Polygons{ square, square2 }, Polygons{ hole }); THEN("difference of a cw from two ccw is a contour with one hole") { REQUIRE(diff.size() == 1); REQUIRE(diff.front().area() == Approx(ExPolygon({ {40, 40}, {0, 40}, {0, 0}, {40, 0} }, { {15, 25}, {25, 25}, {25, 15}, {15, 15} }).area())); @@ -214,7 +214,7 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { Slic3r::Polygon square { { 10, 10 }, { 20, 10 }, { 20, 20 }, { 10, 20 } }; Slic3r::Polyline square_pl = square.split_at_first_point(); WHEN("no-op diff_pl") { - Slic3r::Polylines res = Slic3r::diff_pl({ square_pl }, {}); + Slic3r::Polylines res = Slic3r::diff_pl({ square_pl }, Polygons{}); THEN("returns the right number of polylines") { REQUIRE(res.size() == 1); } From 79546417795d1e04f3b9627871617db2ee68d00e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 3 May 2021 11:50:05 +0200 Subject: [PATCH 147/154] Fixing compilation on C++ conforming compilers --- src/clipper/clipper.hpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 36b9beee5bb..74e6601f96c 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -192,15 +192,6 @@ inline bool Orientation(const Path &poly) { return Area(poly) >= 0; } int PointInPolygon(const IntPoint &pt, const Path &path); Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftEvenOdd); -template -inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftEvenOdd) { - Clipper c; - c.StrictlySimple(true); - c.AddPaths(std::forward(in_polys), ptSubject, true); - Paths out; - c.Execute(ctUnion, out, fillType, fillType); - return out; -} void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); void CleanPolygon(Path& poly, double distance = 1.415); @@ -560,6 +551,16 @@ class clipperException : public std::exception }; //------------------------------------------------------------------------------ +template +inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftEvenOdd) { + Clipper c; + c.StrictlySimple(true); + c.AddPaths(std::forward(in_polys), ptSubject, true); + Paths out; + c.Execute(ctUnion, out, fillType, fillType); + return out; +} + } //ClipperLib namespace #ifdef CLIPPERLIB_NAMESPACE_PREFIX From 65ceff0883b623919ba349c8ee54b6ac43ab9dc8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 3 May 2021 11:55:23 +0200 Subject: [PATCH 148/154] Another fix for C++ conformant compilers --- src/libslic3r/ClipperUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 49a24408928..f8a94ed69bb 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -63,7 +63,7 @@ namespace ClipperUtils { static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree) { struct Inner { - static void PolyTreeToExPolygonsRecursive(ClipperLib::PolyNode &polynode, ExPolygons *expolygons) + static void PolyTreeToExPolygonsRecursive(ClipperLib::PolyNode &&polynode, ExPolygons *expolygons) { size_t cnt = expolygons->size(); expolygons->resize(cnt + 1); @@ -73,7 +73,7 @@ static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree) (*expolygons)[cnt].holes[i].points = std::move(polynode.Childs[i]->Contour); // Add outer polygons contained by (nested within) holes. for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++ j) - PolyTreeToExPolygonsRecursive(*polynode.Childs[i]->Childs[j], expolygons); + PolyTreeToExPolygonsRecursive(std::move(*polynode.Childs[i]->Childs[j]), expolygons); } } From 4c41fb5fa90138e9359bd9d7eb1b741cb9f628bd Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 3 May 2021 14:12:08 +0200 Subject: [PATCH 149/154] Fixing one unit test, which seems to indicate that the refactoring fixed one issue (hopefully it was not that a newly introduced bug hides an old one). --- t/perimeters.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/perimeters.t b/t/perimeters.t index d0657cb23f4..3d3fd3819ca 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -394,9 +394,9 @@ use Slic3r::Test; }); return scalar keys %z_with_bridges; }; - ok $test->(Slic3r::Test::init_print('V', config => $config)) == 1, - 'no overhangs printed with bridge speed'; # except for the first internal solid layers above void - ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 1, + ok $test->(Slic3r::Test::init_print('V', config => $config)) == 2, + 'no overhangs printed with bridge speed'; # except for the two internal solid layers above void + ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 2, 'overhangs printed with bridge speed'; } From bd85c499acf8e6f5ddaaddb14025b84e66eb2d95 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 3 May 2021 15:00:23 +0200 Subject: [PATCH 150/154] Fixing compiler warnings --- src/libslic3r/ClipperUtils.cpp | 11 +---------- src/libslic3r/ClipperUtils.hpp | 12 +++++++----- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index f8a94ed69bb..8bca3b25a3b 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -57,6 +57,7 @@ err: #endif /* CLIPPER_UTILS_DEBUG */ namespace ClipperUtils { + Points EmptyPathsProvider::s_empty_points; Points SinglePathProvider::s_end; } @@ -143,16 +144,6 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths) return out; } -static ClipperLib::Paths safety_offset(const ClipperLib::Paths &paths) -{ - return safety_offset(paths); -} - -static void safety_offset(ClipperLib::Paths *paths) -{ - *paths = safety_offset(*paths); -} - template ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 0d3b986c061..f3adba94e91 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -32,11 +32,11 @@ namespace ClipperUtils { public: struct iterator : public PathsProviderIteratorBase { public: - constexpr const Points& operator*() { assert(false); return *static_cast(nullptr); } + constexpr const Points& operator*() { assert(false); return s_empty_points; } // all iterators point to end. constexpr bool operator==(const iterator &rhs) const { return true; } constexpr bool operator!=(const iterator &rhs) const { return false; } - constexpr const Points& operator++(int) { assert(false); return *static_cast(nullptr); } + constexpr const Points& operator++(int) { assert(false); return s_empty_points; } constexpr iterator& operator++() { assert(false); return *this; } }; @@ -46,6 +46,8 @@ namespace ClipperUtils { static constexpr iterator cbegin() throw() { return cend(); } static constexpr iterator begin() throw() { return cend(); } static constexpr size_t size() throw() { return 0; } + + static Points &s_empty_points; }; class SinglePathProvider { @@ -158,7 +160,7 @@ namespace ClipperUtils { } private: ExPolygons::const_iterator m_it_expolygon; - int m_idx_contour; + size_t m_idx_contour; }; iterator cbegin() const { return iterator(m_expolygons.cbegin()); } @@ -199,7 +201,7 @@ namespace ClipperUtils { } private: Surfaces::const_iterator m_it_surface; - int m_idx_contour; + size_t m_idx_contour; }; iterator cbegin() const { return iterator(m_surfaces.cbegin()); } @@ -240,7 +242,7 @@ namespace ClipperUtils { } private: SurfacesPtr::const_iterator m_it_surface; - int m_idx_contour; + size_t m_idx_contour; }; iterator cbegin() const { return iterator(m_surfaces.cbegin()); } From 7d9cca600c211f0834b62434fabb80d1e8c96c81 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 3 May 2021 15:28:03 +0200 Subject: [PATCH 151/154] Fixing after merge. --- src/libslic3r/ClipperUtils.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index f3adba94e91..f7365a78488 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -32,11 +32,11 @@ namespace ClipperUtils { public: struct iterator : public PathsProviderIteratorBase { public: - constexpr const Points& operator*() { assert(false); return s_empty_points; } + const Points& operator*() { assert(false); return s_empty_points; } // all iterators point to end. constexpr bool operator==(const iterator &rhs) const { return true; } constexpr bool operator!=(const iterator &rhs) const { return false; } - constexpr const Points& operator++(int) { assert(false); return s_empty_points; } + const Points& operator++(int) { assert(false); return s_empty_points; } constexpr iterator& operator++() { assert(false); return *this; } }; From ac4c3080e4ffb375257448ca23c4a6c5435f498e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 3 May 2021 15:30:10 +0200 Subject: [PATCH 152/154] One more fix after merge. --- src/libslic3r/ClipperUtils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index f7365a78488..c64828644be 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -47,7 +47,7 @@ namespace ClipperUtils { static constexpr iterator begin() throw() { return cend(); } static constexpr size_t size() throw() { return 0; } - static Points &s_empty_points; + static Points s_empty_points; }; class SinglePathProvider { From 3756fa857f0a4ad3469b71391cc6b984286fe7b8 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 3 May 2021 15:41:42 +0200 Subject: [PATCH 153/154] 0.0.10 Various updates for Anycubic Mega. Added filament profiles. --- resources/profiles/Anycubic.idx | 27 ++-- resources/profiles/Anycubic.ini | 271 +++++++++++++++++++++++--------- 2 files changed, 208 insertions(+), 90 deletions(-) diff --git a/resources/profiles/Anycubic.idx b/resources/profiles/Anycubic.idx index 24a881f3034..cc3b55ef4cf 100644 --- a/resources/profiles/Anycubic.idx +++ b/resources/profiles/Anycubic.idx @@ -1,13 +1,14 @@ -min_slic3r_version = 2.3.1-beta -0.0.9 Updated bed textures -min_slic3r_version = 2.3.0-beta2 -0.0.8 Updated start and end g-code for Anycubic Mega. -0.0.7 Updated start g-code for Anycubic Mega. -0.0.6 Reduced max print height for Predator. Updated end g-code, before layer change g-code and output filename format for Kossel. -0.0.5 Updated end g-code. -min_slic3r_version = 2.3.0-alpha2 -0.0.4 Fixed predator output filename format, infill overlap, start gcode adjustments. -0.0.3 Fixed infill_overlap, start_gcode, end_gcode for Anycubic Predator -0.0.2 Added Anycubic Predator -min_slic3r_version = 2.3.0-alpha0 -0.0.1 Initial Version +min_slic3r_version = 2.3.1-beta +0.0.10 Various updates for Anycubic Mega. Added filament profiles. +0.0.9 Updated bed textures +min_slic3r_version = 2.3.0-beta2 +0.0.8 Updated start and end g-code for Anycubic Mega. +0.0.7 Updated start g-code for Anycubic Mega. +0.0.6 Reduced max print height for Predator. Updated end g-code, before layer change g-code and output filename format for Kossel. +0.0.5 Updated end g-code. +min_slic3r_version = 2.3.0-alpha2 +0.0.4 Fixed predator output filename format, infill overlap, start gcode adjustments. +0.0.3 Fixed infill_overlap, start_gcode, end_gcode for Anycubic Predator +0.0.2 Added Anycubic Predator +min_slic3r_version = 2.3.0-alpha0 +0.0.1 Initial Version diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index 44308abc891..ff036729107 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -5,7 +5,7 @@ name = Anycubic # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.9 +config_version = 0.0.10 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Anycubic/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -76,7 +76,7 @@ bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 -compatible_printers = +compatible_printers = complete_objects = 0 dont_support_bridges = 1 elefant_foot_compensation = 0 @@ -108,7 +108,7 @@ max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 -notes = +notes = overhangs = 0 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 @@ -117,8 +117,8 @@ perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 -post_process = -print_settings_id = +post_process = +print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest @@ -290,7 +290,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ and n # Common filament preset [filament:*common_akossel*] cooling = 0 -compatible_printers = +compatible_printers = extrusion_multiplier = 1 filament_cost = 0 filament_density = 0 @@ -375,9 +375,9 @@ filament_vendor = Generic # Common printer preset [printer:*common_akossel*] printer_technology = FFF -bed_shape = +bed_shape = before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0\n;[layer_z] -between_objects_gcode = +between_objects_gcode = deretract_speed = 40 extruder_colour = #FFFF00 extruder_offset = 0x0 @@ -405,8 +405,8 @@ max_layer_height = 0.3 min_layer_height = 0.08 max_print_height = 300 nozzle_diameter = 0.4 -printer_notes = -printer_settings_id = +printer_notes = +printer_settings_id = retract_before_travel = 2 retract_before_wipe = 70% retract_layer_change = 1 @@ -419,9 +419,9 @@ retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 60 single_extruder_multi_material = 0 -start_gcode = +start_gcode = end_gcode = M104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG28 ; home\nM84 ; disable motors -toolchange_gcode = +toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 @@ -435,7 +435,7 @@ default_filament_profile = Generic PLA @AKOSSEL inherits = *common_akossel* printer_model = AKLP printer_variant = 0.4 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Anycubic\nPRINTER_MODEL_AKLP\nPRINTER_HAS_BOWDEN\n +printer_notes = Do not remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Anycubic\nPRINTER_MODEL_AKLP\nPRINTER_HAS_BOWDEN\n bed_shape = 114.562x10.0229,113.253x19.9695,111.081x29.7642,108.065x39.3323,104.225x48.6011,99.5929x57.5,94.2025x65.9613,88.0951x73.9206,81.3173x81.3173,73.9206x88.0951,65.9613x94.2025,57.5x99.5929,48.6011x104.225,39.3323x108.065,29.7642x111.081,19.9695x113.253,10.0229x114.562,7.04172e-15x115,-10.0229x114.562,-19.9695x113.253,-29.7642x111.081,-39.3323x108.065,-48.6011x104.225,-57.5x99.5929,-65.9613x94.2025,-73.9206x88.0951,-81.3173x81.3173,-88.0951x73.9206,-94.2025x65.9613,-99.5929x57.5,-104.225x48.6011,-108.065x39.3323,-111.081x29.7642,-113.253x19.9695,-114.562x10.0229,-115x1.40834e-14,-114.562x-10.0229,-113.253x-19.9695,-111.081x-29.7642,-108.065x-39.3323,-104.225x-48.6011,-99.5929x-57.5,-94.2025x-65.9613,-88.0951x-73.9206,-81.3173x-81.3173,-73.9206x-88.0951,-65.9613x-94.2025,-57.5x-99.5929,-48.6011x-104.225,-39.3323x-108.065,-29.7642x-111.081,-19.9695x-113.253,-10.0229x-114.562,-2.11252e-14x-115,10.0229x-114.562,19.9695x-113.253,29.7642x-111.081,39.3323x-108.065,48.6011x-104.225,57.5x-99.5929,65.9613x-94.2025,73.9206x-88.0951,81.3173x-81.3173,88.0951x-73.9206,94.2025x-65.9613,99.5929x-57.5,104.225x-48.6011,108.065x-39.3323,111.081x-29.7642,113.253x-19.9695,114.562x-10.0229,115x-2.81669e-14 start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 ; home\nG1 X-54.672 Y95.203 Z0.3 F9000\nG92 E0.0\nG1 F1000\nG1 X-52.931 Y96.185 E0.300\nG1 X-50.985 Y97.231 E0.331\nG1 X-49.018 Y98.238 E0.331\nG1 X-47.032 Y99.205 E0.331\nG1 X-45.026 Y100.132 E0.331\nG1 X-43.003 Y101.019 E0.331\nG1 X-40.961 Y101.864 E0.331\nG1 X-38.904 Y102.668 E0.331\nG1 X-36.83 Y103.431 E0.331\nG1 X-34.742 Y104.152 E0.331\nG1 X-32.639 Y104.83 E0.331\nG1 X-30.523 Y105.466 E0.331\nG1 X-28.395 Y106.06 E0.331\nG1 X-26.255 Y106.61 E0.331\nG1 X-24.105 Y107.117 E0.331\nG1 X-21.945 Y107.581 E0.331\nG1 X-19.776 Y108.001 E0.331\nG1 X-17.599 Y108.377 E0.331\nG1 X-15.415 Y108.71 E0.331\nG1 X-13.224 Y108.998 E0.331\nG1 X-11.028 Y109.242 E0.331\nG1 X-8.828 Y109.442 E0.331\nG1 X-6.624 Y109.598 E0.331\nG1 X-4.418 Y109.709 E0.331\nG1 X-2.209 Y109.776 E0.332\nG1 X0 Y109.798 E0.331\nG1 X2.209 Y109.776 E0.690\nG1 X4.418 Y109.709 E0.691\nG1 X6.624 Y109.598 E0.690\nG1 X8.828 Y109.442 E0.690\nG1 X11.028 Y109.242 E0.690\nG1 X13.224 Y108.998 E0.690\nG1 X15.415 Y108.71 E0.691\nG1 X17.599 Y108.377 E0.690\nG1 X19.776 Y108.001 E0.690\nG1 X21.945 Y107.581 E0.690\nG1 X24.105 Y107.117 E0.690\nG1 X26.255 Y106.61 E0.690\nG1 X28.395 Y106.06 E0.690\nG1 X30.523 Y105.466 E0.690\nG1 X32.639 Y104.83 E0.690\nG1 X34.742 Y104.152 E0.690\nG1 X36.83 Y103.431 E0.690\nG1 X38.904 Y102.668 E0.691\nG1 X40.961 Y101.864 E0.690\nG1 X43.003 Y101.019 E0.691\nG1 X45.026 Y100.132 E0.690\nG1 X47.032 Y99.205 E0.691\nG1 X49.018 Y98.238 E0.690\nG1 X50.985 Y97.231 E0.691\nG1 X52.931 Y96.185 E0.690\nG1 X54.672 Y95.203 E0.625\nG92 E0.0\nG1 E-5 F3000 ; retract 5mm\nG1 X52.931 Y96.185 F1000 ; wipe\nG1 X50.985 Y97.231 F1000 ; wipe\nG1 X49.018 Y98.238 F1000 ; wipe\nG1 X0 Y109.798 F1000\nG1 E4.8 F1500; de-retract\nG92 E0.0 ; reset extrusion distance\nM221 S{if layer_height<0.075}100{else}95{endif} @@ -443,7 +443,7 @@ start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 inherits = *common_akossel* printer_model = AK printer_variant = 0.4 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Anycubic\nPRINTER_MODEL_AK\nPRINTER_HAS_BOWDEN\n +printer_notes = Do not remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Anycubic\nPRINTER_MODEL_AK\nPRINTER_HAS_BOWDEN\n bed_shape = 89.6575x7.84402,88.6327x15.6283,86.9333x23.2937,84.5723x30.7818,81.5677x38.0356,77.9423x45,73.7237x51.6219,68.944x57.8509,63.6396x63.6396,57.8509x68.944,51.6219x73.7237,45x77.9423,38.0356x81.5677,30.7818x84.5723,23.2937x86.9333,15.6283x88.6327,7.84402x89.6575,5.51091e-15x90,-7.84402x89.6575,-15.6283x88.6327,-23.2937x86.9333,-30.7818x84.5723,-38.0356x81.5677,-45x77.9423,-51.6219x73.7237,-57.8509x68.944,-63.6396x63.6396,-68.944x57.8509,-73.7237x51.6219,-77.9423x45,-81.5677x38.0356,-84.5723x30.7818,-86.9333x23.2937,-88.6327x15.6283,-89.6575x7.84402,-90x1.10218e-14,-89.6575x-7.84402,-88.6327x-15.6283,-86.9333x-23.2937,-84.5723x-30.7818,-81.5677x-38.0356,-77.9423x-45,-73.7237x-51.6219,-68.944x-57.8509,-63.6396x-63.6396,-57.8509x-68.944,-51.6219x-73.7237,-45x-77.9423,-38.0356x-81.5677,-30.7818x-84.5723,-23.2937x-86.9333,-15.6283x-88.6327,-7.84402x-89.6575,-1.65327e-14x-90,7.84402x-89.6575,15.6283x-88.6327,23.2937x-86.9333,30.7818x-84.5723,38.0356x-81.5677,45x-77.9423,51.6219x-73.7237,57.8509x-68.944,63.6396x-63.6396,68.944x-57.8509,73.7237x-51.6219,77.9423x-45,81.5677x-38.0356,84.5723x-30.7818,86.9333x-23.2937,88.6327x-15.6283,89.6575x-7.84402,90x-2.20436e-14 start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 ; home\nG1 X-39.672 Y69.712 Z0.3 F9000\nG92 E0.0\nG1 F1000\nG1 X-38.457 Y70.397 E0.209\nG1 X-37.043 Y71.157 E0.241\nG1 X-35.614 Y71.889 E0.241\nG1 X-34.171 Y72.591 E0.241\nG1 X-32.714 Y73.265 E0.241\nG1 X-31.244 Y73.909 E0.241\nG1 X-29.761 Y74.523 E0.241\nG1 X-28.266 Y75.108 E0.241\nG1 X-26.759 Y75.662 E0.241\nG1 X-25.242 Y76.185 E0.241\nG1 X-23.714 Y76.678 E0.241\nG1 X-22.177 Y77.14 E0.241\nG1 X-20.63 Y77.571 E0.241\nG1 X-19.076 Y77.971 E0.241\nG1 X-17.514 Y78.34 E0.241\nG1 X-15.944 Y78.677 E0.241\nG1 X-14.368 Y78.982 E0.241\nG1 X-12.786 Y79.255 E0.241\nG1 X-11.199 Y79.497 E0.241\nG1 X-9.608 Y79.706 E0.241\nG1 X-8.013 Y79.884 E0.241\nG1 X-6.414 Y80.029 E0.241\nG1 X-4.813 Y80.142 E0.241\nG1 X-3.21 Y80.223 E0.241\nG1 X-1.605 Y80.271 E0.241\nG1 X0 Y80.287 E0.241\nG1 X1.605 Y80.271 E0.502\nG1 X3.21 Y80.223 E0.502\nG1 X4.813 Y80.142 E0.502\nG1 X6.414 Y80.029 E0.502\nG1 X8.013 Y79.884 E0.502\nG1 X9.608 Y79.706 E0.502\nG1 X11.199 Y79.497 E0.501\nG1 X12.786 Y79.255 E0.502\nG1 X14.368 Y78.982 E0.502\nG1 X15.944 Y78.677 E0.502\nG1 X17.514 Y78.34 E0.502\nG1 X19.076 Y77.971 E0.502\nG1 X20.63 Y77.571 E0.501\nG1 X22.177 Y77.14 E0.502\nG1 X23.714 Y76.678 E0.502\nG1 X25.242 Y76.185 E0.502\nG1 X26.759 Y75.662 E0.501\nG1 X28.266 Y75.108 E0.502\nG1 X29.761 Y74.523 E0.502\nG1 X31.244 Y73.909 E0.502\nG1 X32.714 Y73.265 E0.502\nG1 X34.171 Y72.591 E0.502\nG1 X35.614 Y71.889 E0.501\nG1 X37.043 Y71.157 E0.502\nG1 X38.457 Y70.397 E0.502\nG1 X39.672 Y69.712 E0.436\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} @@ -784,7 +784,7 @@ printer_model = MEGA0 printer_variant = 0.4 max_layer_height = 0.3 min_layer_height = 0.1 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_MEGA0 +printer_notes = Do not remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_MEGA0 bed_shape = 0x0,220x0,220x220,0x220 max_print_height = 250 machine_max_acceleration_e = 5000 @@ -822,54 +822,61 @@ end_gcode = M117 Cooling down...\nM104 S0 ; turn off extruder\nM107 ; Fan off\nM [print:*common_mega*] bottom_solid_min_thickness = 0.5 -bridge_acceleration = 1800 -bridge_flow_ratio = 0.8 +bridge_acceleration = 1000 +bridge_flow_ratio = 0.95 bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_ANYCUBIC.*/ and printer_notes=~/.*PRINTER_MODEL_I3_MEGA.*/ and nozzle_diameter[0]==0.4 -default_acceleration = 1800 +default_acceleration = 1000 ensure_vertical_shell_thickness = 1 -external_perimeter_extrusion_width = 0.6 -external_perimeter_speed = 40 +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 25 extruder_clearance_height = 35 extruder_clearance_radius = 60 extrusion_width = 0.45 fill_density = 15% fill_pattern = gyroid -first_layer_acceleration = 1800 +first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 +first_layer_speed = 20 gap_fill_speed = 40 gcode_comments = 1 -infill_acceleration = 1800 +infill_acceleration = 1000 +infill_anchor = 2.5 +infill_anchor_max = 12 infill_extrusion_width = 0.45 -infill_speed = 60 +max_print_speed = 200 +min_skirt_length = 4 only_retract_when_crossing_perimeters = 0 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode -perimeter_acceleration = 1800 +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode +perimeter_acceleration = 800 perimeter_extrusion_width = 0.45 +perimeter_speed = 45 perimeters = 2 seam_position = nearest -skirts = 0 -slice_closing_radius = 0.05 -small_perimeter_speed = 30 +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 25 solid_infill_below_area = 0 -solid_infill_speed = 60 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 80 support_material_buildplate_only = 1 support_material_contact_distance = 0.1 support_material_extrusion_width = 0.35 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_spacing = 2 +support_material_speed = 50 support_material_threshold = 55 -support_material_with_sheath = 0 thin_walls = 0 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 40 +top_solid_layers = 5 top_solid_min_thickness = 0.6 travel_speed = 180 [print:*supported_mega*] -raft_layers = 2 support_material = 1 # XXXXXXXXXXXXXXXXXXXX @@ -911,7 +918,19 @@ inherits = *0.20mm_mega*;*supported_mega* [print:*0.30mm_mega*] inherits = *common_mega* bottom_solid_layers = 4 -bridge_flow_ratio = 0.95 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 35 +extrusion_width = 0.5 +fill_pattern = grid +infill_extrusion_width = 0.5 +infill_speed = 85 +layer_height = 0.3 +perimeter_extrusion_width = 0.5 +perimeter_speed = 50 +small_perimeter_speed = 30 +solid_infill_extrusion_width = 0.5 +support_material_extrusion_width = 0.38 +support_material_speed = 45 top_solid_layers = 4 [print:0.30mm DRAFT @MEGA] @@ -929,7 +948,6 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_ANYCUBIC.*/ and end_filament_gcode = "; Filament-specific end gcode" fan_always_on = 1 fan_below_layer_time = 100 -filament_colour = #FF8000 filament_vendor = Generic min_print_speed = 15 slowdown_below_layer_time = 20 @@ -941,13 +959,13 @@ slowdown_below_layer_time = 20 cooling = 0 fan_always_on = 0 fan_below_layer_time = 20 - filament_colour = #FFF2EC + filament_colour = #3A80CA filament_cost = 27.82 filament_density = 1.04 filament_max_volumetric_speed = 11 filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" filament_type = ABS - first_layer_bed_temperature = 100 + first_layer_bed_temperature = 105 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 20 @@ -955,14 +973,14 @@ slowdown_below_layer_time = 20 [filament:Generic ABS @MEGA] inherits = *ABS_mega* - + [filament:*FLEX_mega*] inherits = *common_mega* bed_temperature = 50 bridge_fan_speed = 80 cooling = 0 extrusion_multiplier = 1.15 -fan_always_on = 0 +fan_always_on = 0 filament_colour = #008000 filament_cost = 82.00 filament_density = 1.22 @@ -970,7 +988,7 @@ filament_deretract_speed = 25 filament_max_volumetric_speed = 1.2 filament_retract_length = 0.8 filament_type = FLEX -first_layer_bed_temperature = 50 +first_layer_bed_temperature = 55 first_layer_temperature = 240 max_fan_speed = 90 min_fan_speed = 70 @@ -979,16 +997,41 @@ temperature = 240 [filament:Generic FLEX @MEGA] inherits = *FLEX_mega* +[filament:SainSmart TPU @MEGA] +inherits = *FLEX_mega* +filament_vendor = SainSmart +bed_temperature = 50 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 4 +filament_cost = 39.99 +filament_density = 1.21 +filament_deretract_speed = 15 +filament_max_volumetric_speed = 1.8 +filament_notes = "SainSmart TPU gains popularity among 3D Printing community for its balance of rigidity and flexibility. In addition, with a 95A Shore Hardness and improved bed adhesion, it is easier to print even with a stock elementary 3D Printer like the Creality Ender 3. SainSmart TPU will not disappoint if you are looking for flexible filament. From drone parts, phone cases, to small toys, all can be printed with ease.\n\nhttps://www.sainsmart.com/collections/tpu-filament/products/all-colors-tpu-flexible-filament-1-75mm-0-8kg-1-76lb" +filament_retract_before_travel = 5 +filament_retract_length = 4 +filament_retract_speed = 40 +filament_unloading_speed = 90 +first_layer_bed_temperature = 55 +first_layer_temperature = 235 +full_fan_speed_layer = 6 +max_fan_speed = 80 +min_fan_speed = 80 +slowdown_below_layer_time = 10 +temperature = 235 + [filament:*PETG_mega*] inherits = *common_mega* bed_temperature = 90 bridge_fan_speed = 50 fan_below_layer_time = 20 +filament_colour = #FF8000 filament_cost = 27.82 filament_density = 1.27 filament_max_volumetric_speed = 8 filament_type = PETG -first_layer_bed_temperature = 85 +first_layer_bed_temperature = 90 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 @@ -997,14 +1040,63 @@ temperature = 240 [filament:Generic PETG @MEGA] inherits = *PETG_mega* +[filament:ColorFabb XT-CF20 @MEGA] +inherits = *PETG_mega* +compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_notes=~/.*PRINTER_VENDOR_ANYCUBIC.*/ and printer_notes=~/.*PRINTER_MODEL_I3_MEGA.*/ +extrusion_multiplier = 1.05 +filament_colour = #804040 +filament_cost = 66.60 +filament_density = 1.35 +filament_deretract_speed = 25 +filament_max_volumetric_speed = 2 +filament_notes = "Based on colorFabb_XT, XT-CF20 is a carbon fiber composite material. Loaded with no less than 20% specially sourced carbon fibers we have developed a very stiff and tough 3D printing filament made for functional parts. It is truly a professional printers go-to material, especially for users looking for high melt strength, high melt viscosity and good dimensional accuracy and stability.\n\nhttps://colorfabb.com/xt-cf20" +filament_retract_before_travel = 1 +filament_retract_length = 1.4 +filament_retract_speed = 40 +filament_spool_weight = 236 +filament_vendor = ColorFabb +first_layer_temperature = 260 +full_fan_speed_layer = 5 +slowdown_below_layer_time = 15 +temperature = 260 + +[filament:ERYONE PETG @MEGA] +inherits = *PETG_mega* +filament_vendor = ERYONE +filament_cost = 20.99 +filament_notes = "https://eryone.com/petg/show/10.html" + +[filament:FormFutura HDglass @MEGA] +inherits = *PETG_mega* +filament_vendor = FormFutura +filament_cost = 46.65 +filament_notes = "HDglass is a high performance PETG type of 3D printer with unsurpassed 3D printing properties and improved mechanical strength, flexibility, toughness and heat resistance.\n\nhttps://www.formfutura.com/shop/product/hdglass-2812" + +[filament:FormFutura ReForm rPET @MEGA] +inherits = *PETG_mega* +filament_vendor = FormFutura +filament_cost = 26.65 +filament_notes = "ReForm rPET is a recycled PETG type of 3D printer filament that is made from post-industrial waste streams of a nearby located plastic bottle manufacturer.\n\nhttps://www.formfutura.com/shop/product/reform-rpet-2836" +filament_spool_weight = 176 + +[filament:Janbex transparent PETG @MEGA] +inherits = *PETG_mega* +filament_vendor = Janbex +filament_cost = 31.99 +filament_spool_weight = 222 +first_layer_temperature = 215 +min_fan_speed = 100 +temperature = 210 + [filament:*PLA_mega*] inherits = *common_mega* bed_temperature = 60 disable_fan_first_layers = 1 +filament_colour = #FF3232 filament_cost = 25.40 filament_density = 1.24 filament_max_volumetric_speed = 10 -first_layer_bed_temperature = 60 +first_layer_bed_temperature = 65 first_layer_temperature = 215 min_fan_speed = 100 temperature = 210 @@ -1012,73 +1104,97 @@ temperature = 210 [filament:Generic PLA @MEGA] inherits = *PLA_mega* -[filament:*3Dmensionals PLA_mega*] +[filament:3Dmensionals PLA @MEGA] inherits = *PLA_mega* filament_vendor = 3Dmensionals -filament_cost = 23.35 +filament_cost = 22.90 +filament_notes = "Das 3DFilaments - PLA von 3Dmensionals ist ein sehr leicht zu druckendes 3D-Drucker Filament. Dabei handelt es sich um ein etwas härteres PLA mit einer exzellenten thermischen Stabilität. Das Filament zeichnet sich vor allem durch verzugfreies 3D-Drucken aus und weist minimale bis keine Verformung nach dem Abkühlen auf. Daher ist es besonders gut für den Druck größerer Objekte geeignet. Zudem bietet 3DFilaments - PLA über die gesamte Fadenläge eine hervorragende Durchmesser- und Rundheitstoleranz.\n\nhttps://www.3dmensionals.de/3dfilaments?number=PSU3DM001V" -[filament:3Dmensionals PLA @MEGA] -inherits = *3Dmensionals PLA_mega* +[filament:3D Warhorse PLA @MEGA] +inherits = *PLA_mega* +filament_vendor = 3D Warhorse +filament_cost = 19.99 -[filament:3Dmensionals PLA blue @MEGA] -inherits = *3Dmensionals PLA_mega* -filament_colour = #4155FB +[filament:AMOLEN wood PLA] +inherits = *PLA_mega* +filament_vendor = AMOLEN +compatible_printers_condition = nozzle_diameter[0]>0.35 and printer_notes=~/.*PRINTER_VENDOR_ANYCUBIC.*/ and printer_notes=~/.*PRINTER_MODEL_I3_MEGA.*/ +extrusion_multiplier = 1.1 +filament_colour = #DFC287 +filament_cost = 33.99 +filament_density = 1.23 +filament_max_volumetric_speed = 9 +filament_notes = "https://amolen.com/collections/wood/products/amolen-pla-filament-1-75mm-wood-color-3d-printer-filament-1kg2-2lb" -[filament:3Dmensionals PLA silver @MEGA] -inherits = *3Dmensionals PLA_mega* -filament_colour = #B9B5B4 +[filament:FormFutura EasyFil PLA @MEGA] +inherits = *PLA_mega* +filament_vendor = FormFutura +filament_cost = 39.93 +filament_notes = "EasyFil PLA is an easy to print PLA type of 3D printer filament that is available in a wide variety of colors. Its improved flowing behavior make 3D printed layers flow more into each other.\n\nhttps://www.formfutura.com/shop/product/easyfil-pla-2801" -[filament:3Dmensionals PLA white @MEGA] -inherits = *3Dmensionals PLA_mega* -filament_colour = #FEFEFD +[filament:FormFutura ReForm rPLA @MEGA] +inherits = *PLA_mega* +filament_vendor = FormFutura +filament_cost = 26.65 +filament_notes = "ReForm is a sustainable initiative within Formfutura to efficiently manage residual extrusion waste streams and re-use them into high-end upcycled filaments. The ideology behind ReForm is to a make 3D printing more sustainable – without having to make compromises on material properties – and yet keep it affordable.\n\nhttps://www.formfutura.com/shop/product/reform-rpla-2838" -[filament:*Verbatim PLA_mega*] +[filament:GIANTARM PLA @MEGA] +inherits = *PLA_mega* +filament_vendor = GIANTARM +filament_cost = 24.99 + +[filament:Prusament PLA @MEGA] +inherits = *PLA_mega* +filament_vendor = Prusa Polymers +filament_cost = 30.24 +filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" +filament_spool_weight = 201 +temperature = 215 + +[filament:Verbatim PLA @MEGA] inherits = *PLA_mega* filament_vendor = Verbatim filament_cost = 23.88 -[filament:Verbatim PLA @MEGA] -inherits = *Verbatim PLA_mega* - -[filament:Verbatim PLA black @MEGA] -inherits = *Verbatim PLA_mega* -filament_colour = #333333 - [printer:*common_mega*] printer_technology = FFF bed_shape = 0x0,210x0,210x210,0x210 -before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z] +before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z] default_filament_profile = Generic PLA @MEGA default_print_profile = 0.15mm QUALITY @MEGA -deretract_speed = 50 -end_gcode = G4 ; wait\nG92 E0\nG1{if max_layer_z < max_print_height} Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors +deretract_speed = 40 +end_gcode = G1 E-1.0 F2100 ; retract\nG92 E0.0\nG1{if max_layer_z < max_print_height} Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; move print head up\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y105 F3000 ; park print head\nM84 ; disable motors extruder_colour = #808080 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.36 max_print_height = 205 +remaining_times = 1 +retract_before_travel = 1.5 retract_before_wipe = 60% retract_layer_change = 1 -retract_length = 6 -retract_lift = 0.075 +retract_length = 3.2 +retract_lift = 0.2 retract_lift_below = 204 +retract_speed = 70 silent_mode = 0 -start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nG28 ; home all\nG1 Y0 Z1 F100 ; move print head up\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG92 E0\nG1 Z0.2 F360\nG1 X60 E9 F700 ; intro line\nG1 X100 E12.5 F700 ; intro line\nG92 E0 +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nG28 ; home all\nG1 Y2.0 Z0.2 F1000 ; move print head up\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG92 E0.0\nG1 X60.0 E9.0 F1000 ; intro line\nG1 X100.0 E12.5 F1000 ; intro line\nG92 E0.0 +thumbnails = 16x16,220x124 use_relative_e_distances = 1 wipe = 1 -machine_max_acceleration_e = 5000 +machine_max_acceleration_e = 10000 machine_max_acceleration_extruding = 1250 machine_max_acceleration_retracting = 1250 -machine_max_acceleration_x = 1000 -machine_max_acceleration_y = 1000 -machine_max_acceleration_z = 200 +machine_max_acceleration_x = 3000 +machine_max_acceleration_y = 2000 +machine_max_acceleration_z = 60 machine_max_feedrate_e = 60 -machine_max_feedrate_x = 200 -machine_max_feedrate_y = 200 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 machine_max_feedrate_z = 6 machine_max_jerk_e = 5 -machine_max_jerk_x = 8 -machine_max_jerk_y = 8 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 machine_max_jerk_z = 0.4 [printer:Anycubic i3 Mega] @@ -1092,6 +1208,7 @@ inherits = *common_mega* printer_model = I3MEGAS printer_variant = 0.4 printer_notes = Do not remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_I3_MEGA_S\nPRINTER_HAS_BOWDEN +machine_max_feedrate_e = 30 machine_max_feedrate_z = 8 @@ -1743,7 +1860,7 @@ machine_max_jerk_z = 5 machine_min_extruding_rate = 0 machine_min_travel_rate = 0 printer_settings_id = -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PREDATOR\nPRINTER_HAS_BOWDEN\n +printer_notes = Do not remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PREDATOR\nPRINTER_HAS_BOWDEN\n default_filament_profile = Generic PLA @PREDATOR [printer:Anycubic Predator 0.4 nozzle] @@ -1775,4 +1892,4 @@ default_print_profile = 0.24mm 0.8 nozzle DETAILED QUALITY @PREDATOR ######################################### ########## end printer presets ########## -######################################### +#########################################"do not" cause ' is bad for syntax highlighting From c67ec1edbef89590eb61a5a077294846942bd1e9 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 3 May 2021 15:48:05 +0200 Subject: [PATCH 154/154] i3 MEGA S bed model and texture https://github.com/prusa3d/PrusaSlicer/pull/6452 --- resources/profiles/Anycubic/i3megas.svg | 561 ++++++++++++++++++++ resources/profiles/Anycubic/i3megas_bed.stl | Bin 0 -> 18484 bytes 2 files changed, 561 insertions(+) create mode 100644 resources/profiles/Anycubic/i3megas.svg create mode 100644 resources/profiles/Anycubic/i3megas_bed.stl diff --git a/resources/profiles/Anycubic/i3megas.svg b/resources/profiles/Anycubic/i3megas.svg new file mode 100644 index 00000000000..dfb4ae49694 --- /dev/null +++ b/resources/profiles/Anycubic/i3megas.svg @@ -0,0 +1,561 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/profiles/Anycubic/i3megas_bed.stl b/resources/profiles/Anycubic/i3megas_bed.stl new file mode 100644 index 0000000000000000000000000000000000000000..49ff8c5b3b7153dc3c6aea6bceb28b0e2dce3fc6 GIT binary patch literal 18484 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 Kp?9gfa{3SV69`xU literal 0 HcmV?d00001

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⪼njm!po&v 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%0DfJt+2EA^ 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