From 7a4ba7d131328b43f1db6e55aff317ea0837b510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 21 Sep 2020 02:05:52 +0200 Subject: [PATCH 001/225] A simple algorithm to follow the boundary of polygons --- src/libslic3r/GCode.cpp | 87 +++++++++++++++++++++++++++++++++++++++++ src/libslic3r/GCode.hpp | 28 +++++++++++-- 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 61ef9a1bd..10514ef39 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -176,6 +176,93 @@ namespace Slic3r { return islands; } + Matrix2d rotation_by_direction(const Point &direction) + { + Matrix2d rotation; + rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); + rotation(1, 0) = -rotation(0, 1); + rotation(1, 1) = rotation(0, 0); + + return rotation; + } + + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) + { + const Point &start = gcodegen.last_pos(); + const Point &end = point; + const Point direction = end - start; + Matrix2d transform_to_x_axis = rotation_by_direction(direction); + Matrix2d transform_from_x_axis = transform_to_x_axis.transpose(); + + const Line travel_line((transform_to_x_axis * start.cast()).cast(), + (transform_to_x_axis * end.cast()).cast()); + + Polygons borders; + borders.reserve(gcodegen.layer()->lslices.size()); + + for (const ExPolygon &ex_polygon : gcodegen.layer()->lslices) { + borders.emplace_back(ex_polygon.contour); + + for (const Polygon &hole : ex_polygon.holes) borders.emplace_back(hole); + } + + std::vector intersections; + for (size_t border_idx = 0; border_idx < borders.size(); ++border_idx) { + const Polygon &border = borders[border_idx]; + Lines border_lines = border.lines(); + + for (size_t line_idx = 0; line_idx < border_lines.size(); ++line_idx) { + const Line &border_line = border_lines[line_idx]; + Line border_line_transformed((transform_to_x_axis * border_line.a.cast()).cast(), + (transform_to_x_axis * border_line.b.cast()).cast()); + + Point intersection_transformed; + + if (travel_line.intersection(border_line_transformed, &intersection_transformed)) + intersections.emplace_back(border_idx, line_idx, intersection_transformed); + } + } + + // Sort intersections from the nearest to the farthest + std::sort(intersections.begin(), intersections.end()); + + // Polyline result(start, end); + Polyline result; + result.append(start); + + for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + const Intersection &intersection_first = *it_first; + + for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { + const Intersection &intersection_second = *it_second; + + if (intersection_first.border_idx == intersection_second.border_idx) { + Lines border_lines = borders[intersection_first.border_idx].lines(); + + // Append the nearest intersection into the path + result.append((transform_from_x_axis * intersection_first.point.cast()).cast()); + + // Append the path around the border into the path + for (size_t line_idx = intersection_first.line_idx; line_idx != intersection_second.line_idx;) { + result.append(border_lines[line_idx].b); + + if (++line_idx == border_lines.size()) line_idx = 0; + } + + // Append the farthest intersection into the path + result.append((transform_from_x_axis * intersection_second.point.cast()).cast()); + + // Skip intersections in between + it_first = it_second; + break; + } + } + } + + result.append(end); + + return result; + } std::string OozePrevention::pre_toolchange(GCode& gcodegen) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index aeac4fcf8..ad9a2e2fb 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -46,13 +46,13 @@ public: bool disable_once; AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} - ~AvoidCrossingPerimeters() {} + virtual ~AvoidCrossingPerimeters() = default; void reset() { m_external_mp.reset(); m_layer_mp.reset(); } void init_external_mp(const Print &print); void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } - Polyline travel_to(const GCode &gcodegen, const Point &point); + virtual Polyline travel_to(const GCode &gcodegen, const Point &point); private: // For initializing the regions to avoid. @@ -62,6 +62,28 @@ private: std::unique_ptr m_layer_mp; }; +class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters +{ +protected: + struct Intersection + { + size_t border_idx; + size_t line_idx; + Point point; + + Intersection(size_t border_idx, size_t line_idx, Point point) + : border_idx(border_idx), line_idx(line_idx), point(point){}; + + inline bool operator<(const Intersection &other) const { return this->point.x() < other.point.x(); } + }; + +public: + AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} + + virtual ~AvoidCrossingPerimeters2() = default; + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; +}; class OozePrevention { public: @@ -326,7 +348,7 @@ private: std::set m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; - AvoidCrossingPerimeters m_avoid_crossing_perimeters; + AvoidCrossingPerimeters2 m_avoid_crossing_perimeters; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END From 6573bc15a5e274eacf9c8ee76b3037ec66f923ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 21 Sep 2020 14:49:22 +0200 Subject: [PATCH 002/225] Selection of shortest path around polygon boundary --- src/libslic3r/GCode.cpp | 60 ++++++++++++++++++++++++++++++++++------- src/libslic3r/GCode.hpp | 9 +++++++ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 10514ef39..7874cda4f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -176,7 +176,7 @@ namespace Slic3r { return islands; } - Matrix2d rotation_by_direction(const Point &direction) + static Matrix2d rotation_by_direction(const Point &direction) { Matrix2d rotation; rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); @@ -186,6 +186,33 @@ namespace Slic3r { return rotation; } + AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) + { + double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); + double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); + + for (int line_idx = int(start_idx) + 1; line_idx != int(end_idx); ++line_idx) { + if (line_idx == int(lines.size())) line_idx = 0; + + total_length_forward += lines[line_idx].length(); + } + + for (int line_idx = int(start_idx) - 1; line_idx != int(end_idx); --line_idx) { + if (line_idx < 0) line_idx = int(lines.size()) - 1; + + total_length_backward += lines[line_idx].length(); + } + + total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); + total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); + + return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; + } + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) { const Point &start = gcodegen.last_pos(); @@ -213,7 +240,7 @@ namespace Slic3r { for (size_t line_idx = 0; line_idx < border_lines.size(); ++line_idx) { const Line &border_line = border_lines[line_idx]; - Line border_line_transformed((transform_to_x_axis * border_line.a.cast()).cast(), + Line border_line_transformed((transform_to_x_axis * border_line.a.cast()).cast(), (transform_to_x_axis * border_line.b.cast()).cast()); Point intersection_transformed; @@ -226,32 +253,45 @@ namespace Slic3r { // Sort intersections from the nearest to the farthest std::sort(intersections.begin(), intersections.end()); - // Polyline result(start, end); + // Polyline result(start, end); Polyline result; result.append(start); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { const Intersection &intersection_first = *it_first; + Point intersection_first_point((transform_from_x_axis * intersection_first.point.cast()).cast()); for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; + Point intersection_second_point( + (transform_from_x_axis * intersection_second.point.cast()).cast()); if (intersection_first.border_idx == intersection_second.border_idx) { Lines border_lines = borders[intersection_first.border_idx].lines(); - // Append the nearest intersection into the path - result.append((transform_from_x_axis * intersection_first.point.cast()).cast()); + result.append(intersection_first_point); + Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, + intersection_second.line_idx, intersection_first_point, + intersection_second_point); // Append the path around the border into the path - for (size_t line_idx = intersection_first.line_idx; line_idx != intersection_second.line_idx;) { - result.append(border_lines[line_idx].b); + if (shortest_direction == Direction::Forward) { + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); ++line_idx) { + if (line_idx == int(border_lines.size())) line_idx = 0; - if (++line_idx == border_lines.size()) line_idx = 0; + result.append(border_lines[line_idx].b); + } + } else { + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + --line_idx) { + if (line_idx < 0) line_idx = int(border_lines.size()) - 1; + + result.append(border_lines[line_idx].a); + } } // Append the farthest intersection into the path - result.append((transform_from_x_axis * intersection_second.point.cast()).cast()); - + result.append(intersection_second_point); // Skip intersections in between it_first = it_second; break; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index ad9a2e2fb..54ffa209f 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -77,6 +77,15 @@ protected: inline bool operator<(const Intersection &other) const { return this->point.x() < other.point.x(); } }; + enum class Direction { Forward, Backward }; + +private: + static Direction get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last); + public: AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} From 074406647aa926236d05acf992b37c26ea7ff787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 21 Sep 2020 18:17:34 +0200 Subject: [PATCH 003/225] Fixed bug when algorithm stuck in a loop --- src/libslic3r/GCode.cpp | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7874cda4f..8a12068d4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -195,17 +195,20 @@ namespace Slic3r { double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); - for (int line_idx = int(start_idx) + 1; line_idx != int(end_idx); ++line_idx) { - if (line_idx == int(lines.size())) line_idx = 0; + auto cyclic_index = [&lines](int index) { + if (index >= int(lines.size())) + index = 0; + else if (index < 0) + index = lines.size() - 1; + return index; + }; + + for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1)) total_length_forward += lines[line_idx].length(); - } - - for (int line_idx = int(start_idx) - 1; line_idx != int(end_idx); --line_idx) { - if (line_idx < 0) line_idx = int(lines.size()) - 1; + for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1)) total_length_backward += lines[line_idx].length(); - } total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); @@ -275,20 +278,14 @@ namespace Slic3r { intersection_second.line_idx, intersection_first_point, intersection_second_point); // Append the path around the border into the path - if (shortest_direction == Direction::Forward) { - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); ++line_idx) { - if (line_idx == int(border_lines.size())) line_idx = 0; - - result.append(border_lines[line_idx].b); - } - } else { + if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - --line_idx) { - if (line_idx < 0) line_idx = int(border_lines.size()) - 1; - + line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + result.append(border_lines[line_idx].b); + else + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) result.append(border_lines[line_idx].a); - } - } // Append the farthest intersection into the path result.append(intersection_second_point); From 46bae74e48cc3473c89b86c63851101ad69e52a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 7 Oct 2020 14:34:59 +0200 Subject: [PATCH 004/225] Finalization of avoid crossing perimeters algorithm EdgeGrid::Grid is used to find the intersection of a path with a polygon. Simplification of find path. Calculation of boundaries that not be crossed. --- src/libslic3r/GCode.cpp | 244 ++++++++++++++++++++++++++++++++++------ src/libslic3r/GCode.hpp | 9 +- 2 files changed, 215 insertions(+), 38 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8a12068d4..2879fb418 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -46,6 +46,8 @@ using namespace std::literals::string_view_literals; #endif #include +#include +#include namespace Slic3r { @@ -216,76 +218,243 @@ namespace Slic3r { return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } + Polyline AvoidCrossingPerimeters2::simplify_travel(const Polyline &travel, const GCode &gcodegen) + { + struct Visitor + { + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(m_grid); + + Polyline optimized_comb_path; + optimized_comb_path.points.reserve(travel.points.size()); + optimized_comb_path.points.emplace_back(travel.points.front()); + + for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { + const Point ¤t_point = travel.points[point_idx - 1]; + Point next = travel.points[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { + visitor.pt_next = &travel.points[point_idx_2]; + m_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + if (!visitor.intersect) { + next = travel.points[point_idx_2]; + point_idx = point_idx_2; + } + } + + optimized_comb_path.append(next); + } + + return optimized_comb_path; + } + + void AvoidCrossingPerimeters2::init_layer(const Layer &layer) + { + BoundingBox bbox = get_extents(layer.lslices); + bbox.offset(SCALED_EPSILON); + ExPolygons boundaries = get_boundary(layer); + + for (const ExPolygon &ex_polygon : boundaries) { + m_boundaries.emplace_back(ex_polygon.contour); + + for (const Polygon &hole : ex_polygon.holes) m_boundaries.emplace_back(hole); + } + + m_grid.set_bbox(bbox); + m_grid.create(m_boundaries, scale_(10.)); + } + + ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) + { + size_t regions_count = 0; + size_t polygons_count = 0; + long perimeter_spacing = 0; + for (const LayerRegion *layer_region : layer.regions()) { + polygons_count += layer_region->slices.surfaces.size(); + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + perimeter_spacing /= regions_count; + const long offset = perimeter_spacing / 2; + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -offset); + ExPolygons final_boundary; + if (perimeter_boundary.size() != boundary.size()) { + // If any part of the polygon is missing after shrinking, the boundary os slice is used instead. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON)), + offset + SCALED_EPSILON); + perimeter_boundary = offset_ex(perimeter_boundary, offset + SCALED_EPSILON); + perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); + final_boundary = union_ex(perimeter_boundary); + } else { + final_boundary = std::move(perimeter_boundary); + } + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset)); + } + + return final_boundary; + } + + static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) + { + const Point &p0 = polygon.points[(point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1)]; + const Point &p1 = polygon.points[point_idx]; + const Point &p2 = polygon.points[(point_idx >= (polygon.size() - 1)) ? (0) : (point_idx + 1)]; + + assert(p0 != p1); + assert(p1 != p2); + + Vec2d normal_1(-1 * (p1.y() - p0.y()), p1.x() - p0.x()); + Vec2d normal_2(-1 * (p2.y() - p1.y()), p2.x() - p1.x()); + normal_1.normalize(); + normal_2.normalize(); + + return (normal_1 + normal_2).normalized(); + }; + + static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) + { + return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); + } + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) { + bool use_external = this->use_external_mp || this->use_external_mp_once; + if (use_external) { + Point scaled_origin = Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)); + Polyline result = m_external_mp.get()->shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); + result.translate(-scaled_origin); + return result; + } + const Point &start = gcodegen.last_pos(); const Point &end = point; const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); Matrix2d transform_from_x_axis = transform_to_x_axis.transpose(); + const Line travel_line_orig(start, end); const Line travel_line((transform_to_x_axis * start.cast()).cast(), (transform_to_x_axis * end.cast()).cast()); - Polygons borders; - borders.reserve(gcodegen.layer()->lslices.size()); - - for (const ExPolygon &ex_polygon : gcodegen.layer()->lslices) { - borders.emplace_back(ex_polygon.contour); - - for (const Polygon &hole : ex_polygon.holes) borders.emplace_back(hole); - } - std::vector intersections; - for (size_t border_idx = 0; border_idx < borders.size(); ++border_idx) { - const Polygon &border = borders[border_idx]; - Lines border_lines = border.lines(); + { + struct Visitor + { + Visitor(const EdgeGrid::Grid & grid, + std::vector &intersections, + const Matrix2d & transform_to_x_axis, + const Line & travel_line) + : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + {} - for (size_t line_idx = 0; line_idx < border_lines.size(); ++line_idx) { - const Line &border_line = border_lines[line_idx]; - Line border_line_transformed((transform_to_x_axis * border_line.a.cast()).cast(), - (transform_to_x_axis * border_line.b.cast()).cast()); + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); - Point intersection_transformed; + Point intersection_point; + if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, + (transform_to_x_axis * intersection_point.cast()).cast()); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } - if (travel_line.intersection(border_line_transformed, &intersection_transformed)) - intersections.emplace_back(border_idx, line_idx, intersection_transformed); - } + const EdgeGrid::Grid &grid; + std::vector &intersections; + const Matrix2d &transform_to_x_axis; + const Line &travel_line; + std::unordered_set, boost::hash>> intersection_set; + } visitor(m_grid, intersections, transform_to_x_axis, travel_line_orig); + + m_grid.visit_cells_intersecting_line(start, end, visitor); } - // Sort intersections from the nearest to the farthest std::sort(intersections.begin(), intersections.end()); - // Polyline result(start, end); Polyline result; result.append(start); - for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { const Intersection &intersection_first = *it_first; - Point intersection_first_point((transform_from_x_axis * intersection_first.point.cast()).cast()); + Point intersection_first_point((transform_from_x_axis * intersection_first.point.cast()).cast()); for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; - Point intersection_second_point( - (transform_from_x_axis * intersection_second.point.cast()).cast()); + Point intersection_second_point((transform_from_x_axis * intersection_second.point.cast()).cast()); if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = borders[intersection_first.border_idx].lines(); + Lines border_lines = m_boundaries[intersection_first.border_idx].lines(); // Append the nearest intersection into the path result.append(intersection_first_point); - Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, - intersection_second.line_idx, intersection_first_point, - intersection_second_point); + Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, + intersection_first_point, intersection_second_point); // Append the path around the border into the path if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(border_lines[line_idx].b); + line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], + (line_idx + 1 == int(m_boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); else for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(border_lines[line_idx].a); + line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) + result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], line_idx + 0, SCALED_EPSILON)); // Append the farthest intersection into the path result.append(intersection_second_point); @@ -297,8 +466,7 @@ namespace Slic3r { } result.append(end); - - return result; + return simplify_travel(result, gcodegen); } std::string OozePrevention::pre_toolchange(GCode& gcodegen) @@ -2264,8 +2432,10 @@ void GCode::process_layer( for (InstanceToPrint &instance_to_print : instances_to_print) { m_config.apply(instance_to_print.print_object.config(), true); m_layer = layers[instance_to_print.layer_id].layer(); - if (m_config.avoid_crossing_perimeters) + if (m_config.avoid_crossing_perimeters) { m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->lslices, true)); + m_avoid_crossing_perimeters.init_layer(*m_layer); + } if (this->config().gcode_label_objects) gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 54ffa209f..428287087 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -54,7 +54,7 @@ public: virtual Polyline travel_to(const GCode &gcodegen, const Point &point); -private: +protected: // For initializing the regions to avoid. static Polygons collect_contours_all_layers(const PrintObjectPtrs& objects); @@ -85,13 +85,20 @@ private: const size_t end_idx, const Point &intersection_first, const Point &intersection_last); + static ExPolygons get_boundary(const Layer &layer); + Polyline simplify_travel(const Polyline &travel, const GCode &gcodegen); + + Polygons m_boundaries; + EdgeGrid::Grid m_grid; public: AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} virtual ~AvoidCrossingPerimeters2() = default; virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; + + void init_layer(const Layer &layer); }; class OozePrevention { From 5c073d0ddb9a7e0b68188c04b7155d884a85ec51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 7 Oct 2020 14:52:57 +0200 Subject: [PATCH 005/225] Improved documentation of avoid crossing perimeters algorithm. --- src/libslic3r/GCode.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2879fb418..b37b9e5cd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -178,6 +178,7 @@ namespace Slic3r { return islands; } + // Create a rotation matrix for projection on the given vector static Matrix2d rotation_by_direction(const Point &direction) { Matrix2d rotation; @@ -188,6 +189,7 @@ namespace Slic3r { return rotation; } + // Returns a direction of the shortest path along the polygon boundary AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, const size_t start_idx, const size_t end_idx, @@ -253,6 +255,7 @@ namespace Slic3r { optimized_comb_path.points.reserve(travel.points.size()); optimized_comb_path.points.emplace_back(travel.points.front()); + // Try to skip some points in the path. for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { const Point ¤t_point = travel.points[point_idx - 1]; Point next = travel.points[point_idx]; @@ -262,6 +265,7 @@ namespace Slic3r { for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { visitor.pt_next = &travel.points[point_idx_2]; m_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary if (!visitor.intersect) { next = travel.points[point_idx_2]; point_idx = point_idx_2; @@ -359,6 +363,7 @@ namespace Slic3r { return (normal_1 + normal_2).normalized(); }; + // Compute offset of polygon's in a direction inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); @@ -446,6 +451,7 @@ namespace Slic3r { Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first_point, intersection_second_point); // Append the path around the border into the path + // Offset of the polygon's point is used to simplify calculation of intersection between boundary if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) From a4fc435f75e105011c3f3ce4ef8342dad751d85b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 7 Oct 2020 15:27:18 +0200 Subject: [PATCH 006/225] Clear generated boundaries from previous layer --- src/libslic3r/GCode.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b37b9e5cd..054826fc1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -280,6 +280,7 @@ namespace Slic3r { void AvoidCrossingPerimeters2::init_layer(const Layer &layer) { + m_boundaries.clear(); BoundingBox bbox = get_extents(layer.lslices); bbox.offset(SCALED_EPSILON); ExPolygons boundaries = get_boundary(layer); From 39e3358af5a55b9c6a2dcb4dcf3b17f1fd1b01d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 8 Oct 2020 03:54:03 +0200 Subject: [PATCH 007/225] Avoid crossing perimeters bugfix --- src/libslic3r/GCode.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 054826fc1..ae8e3cfda 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -263,6 +263,12 @@ namespace Slic3r { visitor.pt_current = ¤t_point; for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { + if (travel.points[point_idx_2] == current_point) { + next = travel.points[point_idx_2]; + point_idx = point_idx_2; + continue; + } + visitor.pt_next = &travel.points[point_idx_2]; m_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); // Check if deleting point causes crossing a boundary @@ -282,6 +288,9 @@ namespace Slic3r { { m_boundaries.clear(); BoundingBox bbox = get_extents(layer.lslices); + // The path could start in the previous layer. Because of it, we need to extend bounding box by the previous layer + if (layer.lower_layer != nullptr) bbox.merge(get_extents(layer.lower_layer->lslices)); + bbox.offset(SCALED_EPSILON); ExPolygons boundaries = get_boundary(layer); @@ -292,7 +301,7 @@ namespace Slic3r { } m_grid.set_bbox(bbox); - m_grid.create(m_boundaries, scale_(10.)); + m_grid.create(m_boundaries, scale_(1.)); } ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) @@ -317,12 +326,12 @@ namespace Slic3r { ExPolygons perimeter_boundary = offset_ex(boundary, -offset); ExPolygons final_boundary; if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, the boundary os slice is used instead. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON)), + // If any part of the polygon is missing after shrinking, the boundary of slice is used instead. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), offset + SCALED_EPSILON); - perimeter_boundary = offset_ex(perimeter_boundary, offset + SCALED_EPSILON); + perimeter_boundary = offset_ex(perimeter_boundary, offset); perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - final_boundary = union_ex(perimeter_boundary); + final_boundary = offset_ex(union_ex(perimeter_boundary), -offset); } else { final_boundary = std::move(perimeter_boundary); } From 556c212f9d0e64d365a96f51666e75f479190a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 13 Oct 2020 22:29:36 +0200 Subject: [PATCH 008/225] Fixed crossing perimeters when option "Wipe while retracting" is enabled --- src/libslic3r/GCode.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ae8e3cfda..b047d748b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3076,9 +3076,18 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // generate G-code for the travel move std::string gcode; - if (needs_retraction) + if (needs_retraction) { + Point last_post_before_retract = this->last_pos(); gcode += this->retract(); - else + // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. + // Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction() + if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once) { + Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract); + retract_travel.points.reserve(retract_travel.points.size() + travel.points.size()); + append(retract_travel.points, travel.points); + travel = std::move(retract_travel); + } + } else // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); From 69658a57d822c52554b25493b7ae1ebb93f47c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 14 Oct 2020 01:02:39 +0200 Subject: [PATCH 009/225] Fixed crossing perimeters in some cases --- src/libslic3r/GCode.cpp | 44 +++++++++++++++++++++++------------------ src/libslic3r/GCode.hpp | 7 ++++--- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b047d748b..3e04805d8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -356,29 +356,38 @@ namespace Slic3r { return final_boundary; } - static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) + static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { - const Point &p0 = polygon.points[(point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1)]; - const Point &p1 = polygon.points[point_idx]; - const Point &p2 = polygon.points[(point_idx >= (polygon.size() - 1)) ? (0) : (point_idx + 1)]; + assert(left != middle); + assert(middle != right); - assert(p0 != p1); - assert(p1 != p2); - - Vec2d normal_1(-1 * (p1.y() - p0.y()), p1.x() - p0.x()); - Vec2d normal_2(-1 * (p2.y() - p1.y()), p2.x() - p1.x()); + Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); + Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); normal_1.normalize(); normal_2.normalize(); return (normal_1 + normal_2).normalized(); }; + static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) + { + const Point &p0 = polygon.points[(point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1)]; + const Point &p1 = polygon.points[point_idx]; + const Point &p2 = polygon.points[(point_idx >= (polygon.size() - 1)) ? (0) : (point_idx + 1)]; + return three_points_inward_normal(p0, p1, p2); + }; + // Compute offset of polygon's in a direction inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); } + static Point get_middle_point_offset(const Point &left, const Point &middle, const Point &right, const int offset) + { + return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); + } + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) { bool use_external = this->use_external_mp || this->use_external_mp_once; @@ -393,7 +402,6 @@ namespace Slic3r { const Point &end = point; const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); - Matrix2d transform_from_x_axis = transform_to_x_axis.transpose(); const Line travel_line_orig(start, end); const Line travel_line((transform_to_x_axis * start.cast()).cast(), @@ -423,7 +431,7 @@ namespace Slic3r { if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast()); + (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); intersection_set.insert(*it_contour_and_segment); } } @@ -447,19 +455,17 @@ namespace Slic3r { result.append(start); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { const Intersection &intersection_first = *it_first; - Point intersection_first_point((transform_from_x_axis * intersection_first.point.cast()).cast()); - for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; - Point intersection_second_point((transform_from_x_axis * intersection_second.point.cast()).cast()); - if (intersection_first.border_idx == intersection_second.border_idx) { Lines border_lines = m_boundaries[intersection_first.border_idx].lines(); + const Line &first_intersected_line = border_lines[intersection_first.line_idx]; + const Line &second_intersected_line = border_lines[intersection_second.line_idx]; // Append the nearest intersection into the path - result.append(intersection_first_point); + result.append(get_middle_point_offset(first_intersected_line.a, intersection_first.point, first_intersected_line.b, SCALED_EPSILON)); Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, - intersection_first_point, intersection_second_point); + intersection_first.point, intersection_second.point); // Append the path around the border into the path // Offset of the polygon's point is used to simplify calculation of intersection between boundary if (shortest_direction == Direction::Forward) @@ -473,9 +479,9 @@ namespace Slic3r { result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], line_idx + 0, SCALED_EPSILON)); // Append the farthest intersection into the path - result.append(intersection_second_point); + result.append(get_middle_point_offset(second_intersected_line.a, intersection_second.point, second_intersected_line.b, SCALED_EPSILON)); // Skip intersections in between - it_first = it_second; + it_first = (it_second - 1); break; } } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 428287087..c13a7d3b5 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -69,12 +69,13 @@ protected: { size_t border_idx; size_t line_idx; + Point point_transformed; Point point; - Intersection(size_t border_idx, size_t line_idx, Point point) - : border_idx(border_idx), line_idx(line_idx), point(point){}; + Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) + : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - inline bool operator<(const Intersection &other) const { return this->point.x() < other.point.x(); } + inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } }; enum class Direction { Forward, Backward }; From c16aad7e0b8f97cb29b0e97547d8dc35a181e4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 01:57:26 +0200 Subject: [PATCH 010/225] Reworked the algorithm for avoid crossing perimeters for multiple objects --- src/libslic3r/GCode.cpp | 175 ++++++++++++++++++++++++++++------- src/libslic3r/GCode.hpp | 13 ++- src/libslic3r/MultiPoint.cpp | 11 +++ src/libslic3r/MultiPoint.hpp | 3 +- 4 files changed, 167 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3e04805d8..fd619f7e5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -220,7 +220,7 @@ namespace Slic3r { return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } - Polyline AvoidCrossingPerimeters2::simplify_travel(const Polyline &travel, const GCode &gcodegen) + Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel) { struct Visitor { @@ -249,7 +249,7 @@ namespace Slic3r { const Slic3r::Point *pt_current = nullptr; const Slic3r::Point *pt_next = nullptr; bool intersect = false; - } visitor(m_grid); + } visitor(edge_grid); Polyline optimized_comb_path; optimized_comb_path.points.reserve(travel.points.size()); @@ -270,7 +270,7 @@ namespace Slic3r { } visitor.pt_next = &travel.points[point_idx_2]; - m_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); // Check if deleting point causes crossing a boundary if (!visitor.intersect) { next = travel.points[point_idx_2]; @@ -287,21 +287,75 @@ namespace Slic3r { void AvoidCrossingPerimeters2::init_layer(const Layer &layer) { m_boundaries.clear(); - BoundingBox bbox = get_extents(layer.lslices); - // The path could start in the previous layer. Because of it, we need to extend bounding box by the previous layer - if (layer.lower_layer != nullptr) bbox.merge(get_extents(layer.lower_layer->lslices)); + m_boundaries_external.clear(); - bbox.offset(SCALED_EPSILON); - ExPolygons boundaries = get_boundary(layer); + ExPolygons boundaries = get_boundary(layer); + ExPolygons boundaries_external = get_boundary_external(layer); - for (const ExPolygon &ex_polygon : boundaries) { - m_boundaries.emplace_back(ex_polygon.contour); + m_bbox = get_extents(boundaries); + m_bbox.offset(SCALED_EPSILON); + m_bbox_external = get_extents(boundaries_external); + m_bbox_external.offset(SCALED_EPSILON); - for (const Polygon &hole : ex_polygon.holes) m_boundaries.emplace_back(hole); + for (const ExPolygon &ex_poly : boundaries) { + m_boundaries.emplace_back(ex_poly.contour); + append(m_boundaries, ex_poly.holes); + } + for (const ExPolygon &ex_poly : boundaries_external) { + m_boundaries_external.emplace_back(ex_poly.contour); + append(m_boundaries_external, ex_poly.holes); } - m_grid.set_bbox(bbox); + m_grid.set_bbox(m_bbox); m_grid.create(m_boundaries, scale_(1.)); + m_grid_external.set_bbox(m_bbox_external); + m_grid_external.create(m_boundaries_external, scale_(1.)); + } + + ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &llayer) + { + size_t regions_count = 0; + long perimeter_spacing = 0; + ExPolygons boundary; + for (const PrintObject *object : llayer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + for (Layer *layer : object->layers()) { + if ((llayer.print_z - EPSILON) <= layer->print_z && layer->print_z <= (llayer.print_z + EPSILON)) { + for (const LayerRegion *layer_region : layer->regions()) { + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + } + } + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.reserve(boundary.size() + polygons_per_obj.size()); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); + } + } + + perimeter_spacing /= regions_count; + const long perimeter_offset = perimeter_spacing / 2; + + Polygons contours; + Polygons holes; + for (ExPolygon &poly : boundary) { + contours.emplace_back(poly.contour); + append(holes, poly.holes); + } + + ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset))); + ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); + final_boundary.reserve(final_boundary.size() + holes_boundary.size()); + final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + final_boundary = union_ex(final_boundary); + return final_boundary; } ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) @@ -331,7 +385,7 @@ namespace Slic3r { offset + SCALED_EPSILON); perimeter_boundary = offset_ex(perimeter_boundary, offset); perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - final_boundary = offset_ex(union_ex(perimeter_boundary), -offset); + final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); } else { final_boundary = std::move(perimeter_boundary); } @@ -388,20 +442,51 @@ namespace Slic3r { return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); } - Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) + bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) { - bool use_external = this->use_external_mp || this->use_external_mp_once; - if (use_external) { - Point scaled_origin = Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)); - Polyline result = m_external_mp.get()->shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); - result.translate(-scaled_origin); - return result; + bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); + // When both endpoints are out of the bounding box, it needs to check in more detail. + if (start_out_of_bound && end_out_of_bound) { + Point intersection; + return bbox.polygon().intersection(Line(start, end), &intersection); + } + return true; + } + + std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) + { + bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); + Point start_clamped = start, end_clamped = end; + Points intersections; + if (start_out_of_bound || end_out_of_bound) { + bbox.polygon().intersections(Line(start, end), &intersections); + assert(intersections.size() <= 2); } - const Point &start = gcodegen.last_pos(); - const Point &end = point; - const Point direction = end - start; - Matrix2d transform_to_x_axis = rotation_by_direction(direction); + if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { + start_clamped = intersections[0]; + } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { + end_clamped = intersections[0]; + } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { + if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { + start_clamped = intersections[0]; + end_clamped = intersections[1]; + } else { + start_clamped = intersections[1]; + end_clamped = intersections[0]; + } + } + + return std::make_pair(start_clamped, end_clamped); + } + + Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons & boundaries, + const EdgeGrid::Grid &edge_grid, + const Point & start, + const Point & end) + { + const Point direction = end - start; + Matrix2d transform_to_x_axis = rotation_by_direction(direction); const Line travel_line_orig(start, end); const Line travel_line((transform_to_x_axis * start.cast()).cast(), @@ -444,9 +529,9 @@ namespace Slic3r { const Matrix2d &transform_to_x_axis; const Line &travel_line; std::unordered_set, boost::hash>> intersection_set; - } visitor(m_grid, intersections, transform_to_x_axis, travel_line_orig); + } visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - m_grid.visit_cells_intersecting_line(start, end, visitor); + edge_grid.visit_cells_intersecting_line(start, end, visitor); } std::sort(intersections.begin(), intersections.end()); @@ -458,8 +543,8 @@ namespace Slic3r { for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = m_boundaries[intersection_first.border_idx].lines(); - const Line &first_intersected_line = border_lines[intersection_first.line_idx]; + Lines border_lines = boundaries[intersection_first.border_idx].lines(); + const Line &first_intersected_line = border_lines[intersection_first.line_idx]; const Line &second_intersected_line = border_lines[intersection_second.line_idx]; // Append the nearest intersection into the path result.append(get_middle_point_offset(first_intersected_line.a, intersection_first.point, first_intersected_line.b, SCALED_EPSILON)); @@ -471,12 +556,12 @@ namespace Slic3r { if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], - (line_idx + 1 == int(m_boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); + result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); else for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], line_idx + 0, SCALED_EPSILON)); + result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); // Append the farthest intersection into the path result.append(get_middle_point_offset(second_intersected_line.a, intersection_second.point, second_intersected_line.b, SCALED_EPSILON)); @@ -488,7 +573,33 @@ namespace Slic3r { } result.append(end); - return simplify_travel(result, gcodegen); + return simplify_travel(edge_grid, result); + } + + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) + { + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = this->use_external_mp || this->use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + Point start = gcodegen.last_pos() + scaled_origin; + Point end = point + scaled_origin; + Polyline result; + if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { + result = Polyline({start, end}); + } else { + auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); + if (use_external) + result = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped); + else + result = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped); + } + + result.points.front() = start; + result.points.back() = end; + if (use_external) + result.translate(-scaled_origin); + return result; } std::string OozePrevention::pre_toolchange(GCode& gcodegen) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index c13a7d3b5..e7a54b571 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -88,10 +88,19 @@ private: const Point &intersection_last); static ExPolygons get_boundary(const Layer &layer); - Polyline simplify_travel(const Polyline &travel, const GCode &gcodegen); + static ExPolygons get_boundary_external(const Layer &layer); - Polygons m_boundaries; + static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); + + static Polyline avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end); + + Polygons m_boundaries; + Polygons m_boundaries_external; + BoundingBox m_bbox; + BoundingBox m_bbox_external; EdgeGrid::Grid m_grid; + EdgeGrid::Grid m_grid_external; + public: AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 39b07e7d8..a00dd802b 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -140,6 +140,17 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const return found; } +bool MultiPoint::intersections(const Line &line, Points *intersections) const +{ + size_t intersections_size = intersections->size(); + for (const Line &polygon_line : this->lines()) { + Point intersection; + if (polygon_line.intersection(line, &intersection)) + intersections->emplace_back(std::move(intersection)); + } + return intersections->size() > intersections_size; +} + std::vector MultiPoint::_douglas_peucker(const std::vector& pts, const double tolerance) { std::vector result_pts; diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 9ff91b502..653e59cd8 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -81,7 +81,8 @@ public: bool intersection(const Line& line, Point* intersection) const; bool first_intersection(const Line& line, Point* intersection) const; - + bool intersections(const Line &line, Points *intersections) const; + static Points _douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); }; From c828a5d6e9fac6de138ab576f02105cfff8dd8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 02:25:45 +0200 Subject: [PATCH 011/225] Added the possibility to set the maximum length of the detour --- src/libslic3r/GCode.cpp | 6 ++++++ src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 1 + src/libslic3r/PrintConfig.cpp | 10 ++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/slic3r/GUI/Tab.cpp | 3 ++- 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index fd619f7e5..56052ebb7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -597,6 +597,12 @@ namespace Slic3r { result.points.front() = start; result.points.back() = end; + + Line travel(start, end); + double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); + if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { + result = Polyline({start, end}); + } if (use_external) result.translate(-scaled_origin); return result; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 199f4e936..b6c63c4fb 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -410,7 +410,7 @@ const std::vector& Preset::print_options() "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing", - "max_print_speed", "max_volumetric_speed", + "max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour", #ifdef HAS_PRESSURE_EQUALIZER "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 47d48dd40..53b24a01e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -71,6 +71,7 @@ bool Print::invalidate_state_by_config_options(const std::vector steps_gcode = { "avoid_crossing_perimeters", + "avoid_crossing_perimeters_max_detour", "bed_shape", "bed_temperature", "before_layer_gcode", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e62347487..3de4f3e0e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -180,6 +180,16 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("avoid_crossing_perimeters_max_detour", coFloat); + def->label = L("Avoid crossing perimeters - The max detour lenght"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("The maximum detour length for avoid crossing perimeters. " + "If the detour is longer than this value, avoid crossing perimeters is not applied for this path."); + def->sidetext = L("mm (zero to disable)"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("bed_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers after the first one. " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 89c9c7a97..dfb336676 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -821,6 +821,7 @@ class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig public: ConfigOptionBool avoid_crossing_perimeters; + ConfigOptionFloat avoid_crossing_perimeters_max_detour; ConfigOptionPoints bed_shape; ConfigOptionInts bed_temperature; ConfigOptionFloat bridge_acceleration; @@ -894,6 +895,7 @@ protected: 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); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 45ee92c74..399751e11 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -530,7 +530,7 @@ void Tab::decorate() wxColour* colored_label_clr = nullptr; if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || - opt.first == "compatible_prints" || opt.first == "compatible_printers") + opt.first == "compatible_prints" || opt.first == "compatible_printers") colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : m_colored_Label_colors.at(opt.first); if (!colored_label_clr) { @@ -1410,6 +1410,7 @@ void TabPrint::build() optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed"); optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness"); optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters"); + optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour"); optgroup->append_single_option_line("thin_walls", category_path + "detect-thin-walls"); optgroup->append_single_option_line("overhangs", category_path + "detect-bridging-perimeters"); From 4288be0e06885c668a76b2ed07b417a93d70d9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 07:39:15 +0200 Subject: [PATCH 012/225] Fixed a case when the intersection is one of the endpoints of the line --- src/libslic3r/GCode.cpp | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 56052ebb7..22297796e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -410,6 +410,19 @@ namespace Slic3r { return final_boundary; } + static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) + { + if (point != polygon.points[point_idx]) + return polygon.points[point_idx]; + + int line_idx = point_idx; + if (forward) + for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); + else + for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); + return polygon.points[line_idx]; + } + static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); @@ -425,11 +438,13 @@ namespace Slic3r { static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { - const Point &p0 = polygon.points[(point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1)]; - const Point &p1 = polygon.points[point_idx]; - const Point &p2 = polygon.points[(point_idx >= (polygon.size() - 1)) ? (0) : (point_idx + 1)]; - return three_points_inward_normal(p0, p1, p2); - }; + const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); + const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); + const Point &middle = polygon.points[point_idx]; + const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); + const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + return three_points_inward_normal(left, middle, right); + } // Compute offset of polygon's in a direction inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) @@ -437,8 +452,10 @@ namespace Slic3r { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); } - static Point get_middle_point_offset(const Point &left, const Point &middle, const Point &right, const int offset) + static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) { + const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); + const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); } @@ -543,11 +560,11 @@ namespace Slic3r { for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = boundaries[intersection_first.border_idx].lines(); - const Line &first_intersected_line = border_lines[intersection_first.line_idx]; - const Line &second_intersected_line = border_lines[intersection_second.line_idx]; + Lines border_lines = boundaries[intersection_first.border_idx].lines(); // Append the nearest intersection into the path - result.append(get_middle_point_offset(first_intersected_line.a, intersection_first.point, first_intersected_line.b, SCALED_EPSILON)); + size_t left_idx = intersection_first.line_idx; + size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); @@ -564,7 +581,9 @@ namespace Slic3r { result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); // Append the farthest intersection into the path - result.append(get_middle_point_offset(second_intersected_line.a, intersection_second.point, second_intersected_line.b, SCALED_EPSILON)); + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); // Skip intersections in between it_first = (it_second - 1); break; From 20916e23628d9ed70cb311945a3c1f5750f3722e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 07:48:05 +0200 Subject: [PATCH 013/225] Disable filed with max detour length when avoid crossing perimeters is disabled --- src/slic3r/GUI/ConfigManipulation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 845dc1c0b..d23f938f6 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -312,6 +312,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); + + bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters"); + toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters); } void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/) From 8adf02a289490968d32be11337e12ad7959364bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 09:03:49 +0200 Subject: [PATCH 014/225] Moved AvoidCrossingPerimeters to separate file --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 536 ----------------- src/libslic3r/GCode.hpp | 78 +-- .../GCode/AvoidCrossingPerimeters.cpp | 554 ++++++++++++++++++ .../GCode/AvoidCrossingPerimeters.hpp | 101 ++++ 5 files changed, 658 insertions(+), 613 deletions(-) create mode 100644 src/libslic3r/GCode/AvoidCrossingPerimeters.cpp create mode 100644 src/libslic3r/GCode/AvoidCrossingPerimeters.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 150d66d75..93f947545 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -99,6 +99,8 @@ add_library(libslic3r STATIC GCode/WipeTower.hpp GCode/GCodeProcessor.cpp GCode/GCodeProcessor.hpp + GCode/AvoidCrossingPerimeters.cpp + GCode/AvoidCrossingPerimeters.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 22297796e..f152edebf 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -91,542 +91,6 @@ namespace Slic3r { return ok; } - void AvoidCrossingPerimeters::init_external_mp(const Print& print) - { - m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); - } - - // Plan a travel move while minimizing the number of perimeter crossings. - // point is in unscaled coordinates, in the coordinate system of the current active object - // (set by gcodegen.set_origin()). - Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point) - { - // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). - // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; - Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> - shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); - if (use_external) - result.translate(-scaled_origin); - return result; - } - - // Collect outer contours of all objects over all layers. - // Discard objects only containing thin walls (offset would fail on an empty polygon). - // Used by avoid crossing perimeters feature. - Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) - { - Polygons islands; - for (const PrintObject* object : objects) { - // Reducing all the object slices into the Z projection in a logarithimc fashion. - // First reduce to half the number of layers. - std::vector polygons_per_layer((object->layers().size() + 1) / 2); - tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), - [&object, &polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - const Layer* layer1 = object->layers()[i * 2]; - const Layer* layer2 = object->layers()[i * 2 + 1]; - Polygons polys; - polys.reserve(layer1->lslices.size() + layer2->lslices.size()); - for (const ExPolygon& expoly : layer1->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - for (const ExPolygon& expoly : layer2->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer[i] = union_(polys); - } - }); - if (object->layers().size() & 1) { - const Layer* layer = object->layers().back(); - Polygons polys; - polys.reserve(layer->lslices.size()); - for (const ExPolygon& expoly : layer->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer.back() = union_(polys); - } - // Now reduce down to a single layer. - size_t cnt = polygons_per_layer.size(); - while (cnt > 1) { - tbb::parallel_for(tbb::blocked_range(0, cnt / 2), - [&polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - Polygons polys; - polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); - polygons_append(polys, polygons_per_layer[i * 2]); - polygons_append(polys, polygons_per_layer[i * 2 + 1]); - polygons_per_layer[i * 2] = union_(polys); - } - }); - for (size_t i = 1; i < cnt / 2; ++i) - polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); - if (cnt & 1) - polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); - cnt = (cnt + 1) / 2; - } - // And collect copies of the objects. - for (const PrintInstance& instance : object->instances()) { - // All the layers were reduced to the 1st item of polygons_per_layer. - size_t i = islands.size(); - polygons_append(islands, polygons_per_layer.front()); - for (; i < islands.size(); ++i) - islands[i].translate(instance.shift); - } - } - return islands; - } - - // Create a rotation matrix for projection on the given vector - static Matrix2d rotation_by_direction(const Point &direction) - { - Matrix2d rotation; - rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); - rotation(1, 0) = -rotation(0, 1); - rotation(1, 1) = rotation(0, 0); - - return rotation; - } - - // Returns a direction of the shortest path along the polygon boundary - AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) - { - double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); - double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); - - auto cyclic_index = [&lines](int index) { - if (index >= int(lines.size())) - index = 0; - else if (index < 0) - index = lines.size() - 1; - - return index; - }; - - for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1)) - total_length_forward += lines[line_idx].length(); - - for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1)) - total_length_backward += lines[line_idx].length(); - - total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); - total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); - - return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; - } - - Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel) - { - struct Visitor - { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} - - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - - Polyline optimized_comb_path; - optimized_comb_path.points.reserve(travel.points.size()); - optimized_comb_path.points.emplace_back(travel.points.front()); - - // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { - const Point ¤t_point = travel.points[point_idx - 1]; - Point next = travel.points[point_idx]; - - visitor.pt_current = ¤t_point; - - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { - if (travel.points[point_idx_2] == current_point) { - next = travel.points[point_idx_2]; - point_idx = point_idx_2; - continue; - } - - visitor.pt_next = &travel.points[point_idx_2]; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel.points[point_idx_2]; - point_idx = point_idx_2; - } - } - - optimized_comb_path.append(next); - } - - return optimized_comb_path; - } - - void AvoidCrossingPerimeters2::init_layer(const Layer &layer) - { - m_boundaries.clear(); - m_boundaries_external.clear(); - - ExPolygons boundaries = get_boundary(layer); - ExPolygons boundaries_external = get_boundary_external(layer); - - m_bbox = get_extents(boundaries); - m_bbox.offset(SCALED_EPSILON); - m_bbox_external = get_extents(boundaries_external); - m_bbox_external.offset(SCALED_EPSILON); - - for (const ExPolygon &ex_poly : boundaries) { - m_boundaries.emplace_back(ex_poly.contour); - append(m_boundaries, ex_poly.holes); - } - for (const ExPolygon &ex_poly : boundaries_external) { - m_boundaries_external.emplace_back(ex_poly.contour); - append(m_boundaries_external, ex_poly.holes); - } - - m_grid.set_bbox(m_bbox); - m_grid.create(m_boundaries, scale_(1.)); - m_grid_external.set_bbox(m_bbox_external); - m_grid_external.create(m_boundaries_external, scale_(1.)); - } - - ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &llayer) - { - size_t regions_count = 0; - long perimeter_spacing = 0; - ExPolygons boundary; - for (const PrintObject *object : llayer.object()->print()->objects()) { - ExPolygons polygons_per_obj; - for (Layer *layer : object->layers()) { - if ((llayer.print_z - EPSILON) <= layer->print_z && layer->print_z <= (llayer.print_z + EPSILON)) { - for (const LayerRegion *layer_region : layer->regions()) { - for (const Surface &surface : layer_region->slices.surfaces) - polygons_per_obj.emplace_back(surface.expolygon); - - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - } - } - - for (const PrintInstance &instance : object->instances()) { - size_t boundary_idx = boundary.size(); - boundary.reserve(boundary.size() + polygons_per_obj.size()); - boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); - for (; boundary_idx < boundary.size(); ++boundary_idx) - boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); - } - } - - perimeter_spacing /= regions_count; - const long perimeter_offset = perimeter_spacing / 2; - - Polygons contours; - Polygons holes; - for (ExPolygon &poly : boundary) { - contours.emplace_back(poly.contour); - append(holes, poly.holes); - } - - ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset))); - ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); - final_boundary.reserve(final_boundary.size() + holes_boundary.size()); - final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - final_boundary = union_ex(final_boundary); - return final_boundary; - } - - ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) - { - size_t regions_count = 0; - size_t polygons_count = 0; - long perimeter_spacing = 0; - for (const LayerRegion *layer_region : layer.regions()) { - polygons_count += layer_region->slices.surfaces.size(); - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - perimeter_spacing /= regions_count; - const long offset = perimeter_spacing / 2; - - ExPolygons boundary; - boundary.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); - - boundary = union_ex(boundary); - ExPolygons perimeter_boundary = offset_ex(boundary, -offset); - ExPolygons final_boundary; - if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, the boundary of slice is used instead. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), - offset + SCALED_EPSILON); - perimeter_boundary = offset_ex(perimeter_boundary, offset); - perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); - } else { - final_boundary = std::move(perimeter_boundary); - } - - // Collect all top layers that will not be crossed. - polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) ++polygons_count; - - if (polygons_count > 0) { - ExPolygons top_layer_polygons; - top_layer_polygons.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); - - top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset)); - } - - return final_boundary; - } - - static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) - { - if (point != polygon.points[point_idx]) - return polygon.points[point_idx]; - - int line_idx = point_idx; - if (forward) - for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); - else - for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); - return polygon.points[line_idx]; - } - - static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) - { - assert(left != middle); - assert(middle != right); - - Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); - Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); - normal_1.normalize(); - normal_2.normalize(); - - return (normal_1 + normal_2).normalized(); - }; - - static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) - { - const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); - const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); - const Point &middle = polygon.points[point_idx]; - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); - return three_points_inward_normal(left, middle, right); - } - - // Compute offset of polygon's in a direction inward normal - static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) - { - return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); - } - - static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) - { - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); - return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); - } - - bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) - { - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - // When both endpoints are out of the bounding box, it needs to check in more detail. - if (start_out_of_bound && end_out_of_bound) { - Point intersection; - return bbox.polygon().intersection(Line(start, end), &intersection); - } - return true; - } - - std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) - { - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - Point start_clamped = start, end_clamped = end; - Points intersections; - if (start_out_of_bound || end_out_of_bound) { - bbox.polygon().intersections(Line(start, end), &intersections); - assert(intersections.size() <= 2); - } - - if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { - start_clamped = intersections[0]; - } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { - end_clamped = intersections[0]; - } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { - if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { - start_clamped = intersections[0]; - end_clamped = intersections[1]; - } else { - start_clamped = intersections[1]; - end_clamped = intersections[0]; - } - } - - return std::make_pair(start_clamped, end_clamped); - } - - Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons & boundaries, - const EdgeGrid::Grid &edge_grid, - const Point & start, - const Point & end) - { - const Point direction = end - start; - Matrix2d transform_to_x_axis = rotation_by_direction(direction); - - const Line travel_line_orig(start, end); - const Line travel_line((transform_to_x_axis * start.cast()).cast(), - (transform_to_x_axis * end.cast()).cast()); - - std::vector intersections; - { - struct Visitor - { - Visitor(const EdgeGrid::Grid & grid, - std::vector &intersections, - const Matrix2d & transform_to_x_axis, - const Line & travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) - {} - - bool operator()(coord_t iy, coord_t ix) - { - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - - Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && - intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); - intersection_set.insert(*it_contour_and_segment); - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &intersections; - const Matrix2d &transform_to_x_axis; - const Line &travel_line; - std::unordered_set, boost::hash>> intersection_set; - } visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - - edge_grid.visit_cells_intersecting_line(start, end, visitor); - } - - std::sort(intersections.begin(), intersections.end()); - - Polyline result; - result.append(start); - for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { - const Intersection &intersection_first = *it_first; - for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { - const Intersection &intersection_second = *it_second; - if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = boundaries[intersection_first.border_idx].lines(); - // Append the nearest intersection into the path - size_t left_idx = intersection_first.line_idx; - size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); - - Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, - intersection_first.point, intersection_second.point); - // Append the path around the border into the path - // Offset of the polygon's point is used to simplify calculation of intersection between boundary - if (shortest_direction == Direction::Forward) - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); - else - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); - - // Append the farthest intersection into the path - left_idx = intersection_second.line_idx; - right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); - // Skip intersections in between - it_first = (it_second - 1); - break; - } - } - } - - result.append(end); - return simplify_travel(edge_grid, result); - } - - Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) - { - // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). - // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; - Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - Point start = gcodegen.last_pos() + scaled_origin; - Point end = point + scaled_origin; - Polyline result; - if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result = Polyline({start, end}); - } else { - auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); - if (use_external) - result = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped); - else - result = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped); - } - - result.points.front() = start; - result.points.back() = end; - - Line travel(start, end); - double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); - if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { - result = Polyline({start, end}); - } - if (use_external) - result.translate(-scaled_origin); - return result; - } - std::string OozePrevention::pre_toolchange(GCode& gcodegen) { std::string gcode; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index e7a54b571..60c1dd9e1 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -9,6 +9,7 @@ #include "Point.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" +#include "GCode/AvoidCrossingPerimeters.hpp" #include "GCode/CoolingBuffer.hpp" #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" @@ -34,83 +35,6 @@ namespace { struct Item; } struct PrintInstance; using PrintObjectPtrs = std::vector; -class AvoidCrossingPerimeters { -public: - - // this flag triggers the use of the external configuration space - bool use_external_mp; - bool use_external_mp_once; // just for the next travel move - - // this flag disables avoid_crossing_perimeters just for the next travel move - // we enable it by default for the first travel move in print - bool disable_once; - - AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} - virtual ~AvoidCrossingPerimeters() = default; - - void reset() { m_external_mp.reset(); m_layer_mp.reset(); } - void init_external_mp(const Print &print); - void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point); - -protected: - // For initializing the regions to avoid. - static Polygons collect_contours_all_layers(const PrintObjectPtrs& objects); - - std::unique_ptr m_external_mp; - std::unique_ptr m_layer_mp; -}; - -class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters -{ -protected: - struct Intersection - { - size_t border_idx; - size_t line_idx; - Point point_transformed; - Point point; - - Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) - : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - - inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } - }; - - enum class Direction { Forward, Backward }; - -private: - static Direction get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last); - static ExPolygons get_boundary(const Layer &layer); - - static ExPolygons get_boundary_external(const Layer &layer); - - static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); - - static Polyline avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end); - - Polygons m_boundaries; - Polygons m_boundaries_external; - BoundingBox m_bbox; - BoundingBox m_bbox_external; - EdgeGrid::Grid m_grid; - EdgeGrid::Grid m_grid_external; - -public: - AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} - - virtual ~AvoidCrossingPerimeters2() = default; - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; - - void init_layer(const Layer &layer); -}; - class OozePrevention { public: bool enable; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp new file mode 100644 index 000000000..8fe30b7c1 --- /dev/null +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -0,0 +1,554 @@ +#include "../Layer.hpp" +#include "../MotionPlanner.hpp" +#include "../GCode.hpp" +#include "../MotionPlanner.hpp" +#include "../EdgeGrid.hpp" +#include "../Geometry.hpp" +#include "../ShortestPath.hpp" +#include "../Print.hpp" +#include "../Polygon.hpp" +#include "../ExPolygon.hpp" +#include "../ClipperUtils.hpp" +#include "AvoidCrossingPerimeters.hpp" + +#include + +namespace Slic3r { + +void AvoidCrossingPerimeters::init_external_mp(const Print& print) +{ + m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); +} + +// Plan a travel move while minimizing the number of perimeter crossings. +// point is in unscaled coordinates, in the coordinate system of the current active object +// (set by gcodegen.set_origin()). +Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point) +{ + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = this->use_external_mp || this->use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> + shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); + if (use_external) + result.translate(-scaled_origin); + return result; +} + +// Collect outer contours of all objects over all layers. +// Discard objects only containing thin walls (offset would fail on an empty polygon). +// Used by avoid crossing perimeters feature. +Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) +{ + Polygons islands; + for (const PrintObject* object : objects) { + // Reducing all the object slices into the Z projection in a logarithimc fashion. + // First reduce to half the number of layers. + std::vector polygons_per_layer((object->layers().size() + 1) / 2); + tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), + [&object, &polygons_per_layer](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + const Layer* layer1 = object->layers()[i * 2]; + const Layer* layer2 = object->layers()[i * 2 + 1]; + Polygons polys; + polys.reserve(layer1->lslices.size() + layer2->lslices.size()); + for (const ExPolygon& expoly : layer1->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + for (const ExPolygon& expoly : layer2->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + polygons_per_layer[i] = union_(polys); + } + }); + if (object->layers().size() & 1) { + const Layer* layer = object->layers().back(); + Polygons polys; + polys.reserve(layer->lslices.size()); + for (const ExPolygon& expoly : layer->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + polygons_per_layer.back() = union_(polys); + } + // Now reduce down to a single layer. + size_t cnt = polygons_per_layer.size(); + while (cnt > 1) { + tbb::parallel_for(tbb::blocked_range(0, cnt / 2), + [&polygons_per_layer](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + Polygons polys; + polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); + polygons_append(polys, polygons_per_layer[i * 2]); + polygons_append(polys, polygons_per_layer[i * 2 + 1]); + polygons_per_layer[i * 2] = union_(polys); + } + }); + for (size_t i = 0; i < cnt / 2; ++i) + polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); + if (cnt & 1) + polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); + cnt = (cnt + 1) / 2; + } + // And collect copies of the objects. + for (const PrintInstance& instance : object->instances()) { + // All the layers were reduced to the 1st item of polygons_per_layer. + size_t i = islands.size(); + polygons_append(islands, polygons_per_layer.front()); + for (; i < islands.size(); ++i) + islands[i].translate(instance.shift); + } + } + return islands; +} + +// Create a rotation matrix for projection on the given vector +static Matrix2d rotation_by_direction(const Point &direction) +{ + Matrix2d rotation; + rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); + rotation(1, 0) = -rotation(0, 1); + rotation(1, 1) = rotation(0, 0); + + return rotation; +} + +static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) +{ + if (point != polygon.points[point_idx]) + return polygon.points[point_idx]; + + int line_idx = point_idx; + if (forward) + for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); + else + for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); + return polygon.points[line_idx]; +} + +static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) +{ + assert(left != middle); + assert(middle != right); + + Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); + Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); + normal_1.normalize(); + normal_2.normalize(); + + return (normal_1 + normal_2).normalized(); +}; + +static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) +{ + const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); + const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); + const Point &middle = polygon.points[point_idx]; + const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); + const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + return three_points_inward_normal(left, middle, right); +} + +// Compute offset of polygon's in a direction inward normal +static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) +{ + return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); +} + +static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) +{ + const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); + const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); +} + +static bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) +{ + bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); + // When both endpoints are out of the bounding box, it needs to check in more detail. + if (start_out_of_bound && end_out_of_bound) { + Point intersection; + return bbox.polygon().intersection(Line(start, end), &intersection); + } + return true; +} + +static std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) +{ + bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); + Point start_clamped = start, end_clamped = end; + Points intersections; + if (start_out_of_bound || end_out_of_bound) { + bbox.polygon().intersections(Line(start, end), &intersections); + assert(intersections.size() <= 2); + } + + if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { + start_clamped = intersections[0]; + } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { + end_clamped = intersections[0]; + } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { + if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { + start_clamped = intersections[0]; + end_clamped = intersections[1]; + } else { + start_clamped = intersections[1]; + end_clamped = intersections[0]; + } + } + + return std::make_pair(start_clamped, end_clamped); +} + +ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +{ + size_t regions_count = 0; + size_t polygons_count = 0; + long perimeter_spacing = 0; + for (const LayerRegion *layer_region : layer.regions()) { + polygons_count += layer_region->slices.surfaces.size(); + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + perimeter_spacing /= regions_count; + const long offset = perimeter_spacing / 2; + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -offset); + ExPolygons final_boundary; + if (perimeter_boundary.size() != boundary.size()) { + // If any part of the polygon is missing after shrinking, the boundary of slice is used instead. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), + offset + SCALED_EPSILON); + perimeter_boundary = offset_ex(perimeter_boundary, offset); + perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); + final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); + } else { + final_boundary = std::move(perimeter_boundary); + } + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset)); + } + + return final_boundary; +} + +ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &llayer) +{ + size_t regions_count = 0; + long perimeter_spacing = 0; + ExPolygons boundary; + for (const PrintObject *object : llayer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + for (Layer *layer : object->layers()) { + if ((llayer.print_z - EPSILON) <= layer->print_z && layer->print_z <= (llayer.print_z + EPSILON)) { + for (const LayerRegion *layer_region : layer->regions()) { + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + } + } + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.reserve(boundary.size() + polygons_per_obj.size()); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); + } + } + + perimeter_spacing /= regions_count; + const long perimeter_offset = perimeter_spacing / 2; + + Polygons contours; + Polygons holes; + for (ExPolygon &poly : boundary) { + contours.emplace_back(poly.contour); + append(holes, poly.holes); + } + + ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset))); + ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); + final_boundary.reserve(final_boundary.size() + holes_boundary.size()); + final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + final_boundary = union_ex(final_boundary); + return final_boundary; +} + +// Returns a direction of the shortest path along the polygon boundary +AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) +{ + double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); + double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); + + auto cyclic_index = [&lines](int index) { + if (index >= int(lines.size())) + index = 0; + else if (index < 0) + index = lines.size() - 1; + + return index; + }; + + for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1)) + total_length_forward += lines[line_idx].length(); + + for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1)) + total_length_backward += lines[line_idx].length(); + + total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); + total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); + + return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; +} + +Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel) +{ + struct Visitor + { + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(edge_grid); + + Polyline optimized_comb_path; + optimized_comb_path.points.reserve(travel.points.size()); + optimized_comb_path.points.emplace_back(travel.points.front()); + + // Try to skip some points in the path. + for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { + const Point ¤t_point = travel.points[point_idx - 1]; + Point next = travel.points[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { + if (travel.points[point_idx_2] == current_point) { + next = travel.points[point_idx_2]; + point_idx = point_idx_2; + continue; + } + + visitor.pt_next = &travel.points[point_idx_2]; + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel.points[point_idx_2]; + point_idx = point_idx_2; + } + } + + optimized_comb_path.append(next); + } + + return optimized_comb_path; +} + +Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end) +{ + const Point direction = end - start; + Matrix2d transform_to_x_axis = rotation_by_direction(direction); + + const Line travel_line_orig(start, end); + const Line travel_line((transform_to_x_axis * start.cast()).cast(), + (transform_to_x_axis * end.cast()).cast()); + + std::vector intersections; + { + struct Visitor + { + Visitor(const EdgeGrid::Grid & grid, + std::vector &intersections, + const Matrix2d & transform_to_x_axis, + const Line & travel_line) + : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + {} + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + + Point intersection_point; + if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, + (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + std::vector &intersections; + const Matrix2d &transform_to_x_axis; + const Line &travel_line; + std::unordered_set, boost::hash>> intersection_set; + } visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); + + edge_grid.visit_cells_intersecting_line(start, end, visitor); + } + + std::sort(intersections.begin(), intersections.end()); + + Polyline result; + result.append(start); + for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + const Intersection &intersection_first = *it_first; + for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { + const Intersection &intersection_second = *it_second; + if (intersection_first.border_idx == intersection_second.border_idx) { + Lines border_lines = boundaries[intersection_first.border_idx].lines(); + // Append the nearest intersection into the path + size_t left_idx = intersection_first.line_idx; + size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); + + Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, + intersection_first.point, intersection_second.point); + // Append the path around the border into the path + // Offset of the polygon's point is used to simplify calculation of intersection between boundary + if (shortest_direction == Direction::Forward) + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); + else + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) + result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); + + // Append the farthest intersection into the path + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); + // Skip intersections in between + it_first = (it_second - 1); + break; + } + } + } + + result.append(end); + return simplify_travel(edge_grid, result); +} + +Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) +{ + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = this->use_external_mp || this->use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + Point start = gcodegen.last_pos() + scaled_origin; + Point end = point + scaled_origin; + Polyline result; + if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { + result = Polyline({start, end}); + } else { + auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); + if (use_external) + result = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped); + else + result = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped); + } + + result.points.front() = start; + result.points.back() = end; + + Line travel(start, end); + double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); + if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { + result = Polyline({start, end}); + } + if (use_external) + result.translate(-scaled_origin); + return result; +} + +void AvoidCrossingPerimeters2::init_layer(const Layer &layer) +{ + m_boundaries.clear(); + m_boundaries_external.clear(); + + ExPolygons boundaries = get_boundary(layer); + ExPolygons boundaries_external = get_boundary_external(layer); + + m_bbox = get_extents(boundaries); + m_bbox.offset(SCALED_EPSILON); + m_bbox_external = get_extents(boundaries_external); + m_bbox_external.offset(SCALED_EPSILON); + + for (const ExPolygon &ex_poly : boundaries) { + m_boundaries.emplace_back(ex_poly.contour); + append(m_boundaries, ex_poly.holes); + } + for (const ExPolygon &ex_poly : boundaries_external) { + m_boundaries_external.emplace_back(ex_poly.contour); + append(m_boundaries_external, ex_poly.holes); + } + + m_grid.set_bbox(m_bbox); + m_grid.create(m_boundaries, scale_(1.)); + m_grid_external.set_bbox(m_bbox_external); + m_grid_external.create(m_boundaries_external, scale_(1.)); +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp new file mode 100644 index 000000000..bc1f79654 --- /dev/null +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -0,0 +1,101 @@ +#ifndef slic3r_AvoidCrossingPerimeters_hpp_ +#define slic3r_AvoidCrossingPerimeters_hpp_ + +#include "../libslic3r.h" +#include "../ExPolygon.hpp" +#include "../EdgeGrid.hpp" + +namespace Slic3r { + +// Forward declarations. +class GCode; +class Layer; +class MotionPlanner; +class Point; +class Print; +class PrintObject; + +struct PrintInstance; +using PrintObjectPtrs = std::vector; + +class AvoidCrossingPerimeters +{ +public: + // this flag triggers the use of the external configuration space + bool use_external_mp; + bool use_external_mp_once; // just for the next travel move + + // this flag disables avoid_crossing_perimeters just for the next travel move + // we enable it by default for the first travel move in print + bool disable_once; + + AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} + virtual ~AvoidCrossingPerimeters() = default; + + void reset() + { + m_external_mp.reset(); + m_layer_mp.reset(); + } + void init_external_mp(const Print &print); + void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point); + +protected: + // For initializing the regions to avoid. + static Polygons collect_contours_all_layers(const PrintObjectPtrs &objects); + + std::unique_ptr m_external_mp; + std::unique_ptr m_layer_mp; +}; + +class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters +{ +protected: + struct Intersection + { + size_t border_idx; + size_t line_idx; + Point point_transformed; + Point point; + + Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) + : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; + + inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } + }; + + enum class Direction { Forward, Backward }; + +private: + static ExPolygons get_boundary(const Layer &layer); + + static ExPolygons get_boundary_external(const Layer &layer); + + static Direction get_shortest_direction( + const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); + + static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); + + static Polyline avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end); + + Polygons m_boundaries; + Polygons m_boundaries_external; + BoundingBox m_bbox; + BoundingBox m_bbox_external; + EdgeGrid::Grid m_grid; + EdgeGrid::Grid m_grid_external; + +public: + AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} + + virtual ~AvoidCrossingPerimeters2() = default; + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; + + void init_layer(const Layer &layer); +}; +} // namespace Slic3r + +#endif // slic3r_AvoidCrossingPerimeters_hpp_ \ No newline at end of file From 3e98e2a4bdc21ec4999d796bbb146c1b1e103fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 10:56:19 +0200 Subject: [PATCH 015/225] Fixed avoiding of other printed objects, again Calling std::move on itself causes that the first polygon is empty, which results in disabling this feature on Linux. This was fixed before, but I accidentally reverted it when AvoidCrossingPerimeters was moved to separate file. --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 8fe30b7c1..1581eb7ad 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -84,7 +84,7 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP polygons_per_layer[i * 2] = union_(polys); } }); - for (size_t i = 0; i < cnt / 2; ++i) + for (size_t i = 1; i < cnt / 2; ++i) polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); if (cnt & 1) polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); From 2afeea5b6fedc606c52068481f804b60100d3dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 14:37:16 +0200 Subject: [PATCH 016/225] Fixed division by zero when the layer is empty --- .../GCode/AvoidCrossingPerimeters.cpp | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 1581eb7ad..d510156c6 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -200,18 +200,56 @@ static std::pair clamp_endpoints_by_bounding_box(const BoundingBox return std::make_pair(start_clamped, end_clamped); } -ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +static inline coord_t get_default_perimeter_spacing(const Print &print) { - size_t regions_count = 0; - size_t polygons_count = 0; - long perimeter_spacing = 0; + const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; + return scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end())); +} + +static coord_t get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + coord_t perimeter_spacing = 0; for (const LayerRegion *layer_region : layer.regions()) { - polygons_count += layer_region->slices.surfaces.size(); perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++regions_count; } - perimeter_spacing /= regions_count; - const long offset = perimeter_spacing / 2; + + assert(perimeter_spacing >= 0); + if (regions_count != 0) + perimeter_spacing /= regions_count; + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +static coord_t get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + coord_t perimeter_spacing = 0; + for (const PrintObject *object : layer.object()->print()->objects()) + for (Layer *l : object->layers()) + if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) + for (const LayerRegion *layer_region : l->regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + + assert(perimeter_spacing >= 0); + if (regions_count != 0) + perimeter_spacing /= regions_count; + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +{ + const coord_t perimeter_spacing = get_perimeter_spacing(layer); + const coord_t offset = perimeter_spacing / 2; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); ExPolygons boundary; boundary.reserve(polygons_count); @@ -252,36 +290,27 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) return final_boundary; } -ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &llayer) +ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) { - size_t regions_count = 0; - long perimeter_spacing = 0; ExPolygons boundary; - for (const PrintObject *object : llayer.object()->print()->objects()) { + for (const PrintObject *object : layer.object()->print()->objects()) { ExPolygons polygons_per_obj; - for (Layer *layer : object->layers()) { - if ((llayer.print_z - EPSILON) <= layer->print_z && layer->print_z <= (llayer.print_z + EPSILON)) { - for (const LayerRegion *layer_region : layer->regions()) { + for (Layer *l : object->layers()) + if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) + for (const LayerRegion *layer_region : l->regions()) for (const Surface &surface : layer_region->slices.surfaces) polygons_per_obj.emplace_back(surface.expolygon); - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - } - } - for (const PrintInstance &instance : object->instances()) { size_t boundary_idx = boundary.size(); boundary.reserve(boundary.size() + polygons_per_obj.size()); boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); - for (; boundary_idx < boundary.size(); ++boundary_idx) - boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); + for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); } } - perimeter_spacing /= regions_count; - const long perimeter_offset = perimeter_spacing / 2; + const coord_t perimeter_spacing = get_perimeter_spacing_external(layer); + const coord_t perimeter_offset = perimeter_spacing / 2; Polygons contours; Polygons holes; From 7f94e9fa5939ace11719d8de4a7b9b05d28c6639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 19 Oct 2020 09:44:33 +0200 Subject: [PATCH 017/225] Fixed perimeters crossing when supports are printed. --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index d510156c6..c5e43a65a 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -264,12 +264,19 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), offset + SCALED_EPSILON); perimeter_boundary = offset_ex(perimeter_boundary, offset); + perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); } else { final_boundary = std::move(perimeter_boundary); } + // Add an outer boundary to avoid crossing perimeters from supports + ExPolygons outer_boundary = diff_ex(offset_ex(boundary, 2 * perimeter_spacing), offset_ex(boundary, 2 * perimeter_spacing - offset)); + final_boundary.reserve(final_boundary.size() + outer_boundary.size()); + final_boundary.insert(final_boundary.begin(), outer_boundary.begin(), outer_boundary.end()); + final_boundary = union_ex(final_boundary); + // Collect all top layers that will not be crossed. polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) From ef9de07740622e238e21d4c6a795eb9765408a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 22 Oct 2020 06:20:38 +0200 Subject: [PATCH 018/225] Disabling wipe for avoid crossing perimeters --- src/libslic3r/GCode.cpp | 9 +- src/libslic3r/GCode.hpp | 1 + .../GCode/AvoidCrossingPerimeters.cpp | 105 +++++++++++++----- .../GCode/AvoidCrossingPerimeters.hpp | 36 +++++- 4 files changed, 119 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f152edebf..f1c8f5bbf 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2662,14 +2662,16 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string travel.append(point); // check whether a straight travel move would need retraction - bool needs_retraction = this->needs_retraction(travel, role); + bool needs_retraction = this->needs_retraction(travel, role); + // check whether wipe could be disabled without causing visible stringing + bool could_be_wipe_disabled = false; // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a // multi-hop travel path inside the configuration space if (needs_retraction && m_config.avoid_crossing_perimeters && ! m_avoid_crossing_perimeters.disable_once) { - travel = m_avoid_crossing_perimeters.travel_to(*this, point); + travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role); @@ -2683,6 +2685,9 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // generate G-code for the travel move std::string gcode; if (needs_retraction) { + if (m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once && could_be_wipe_disabled) + m_wipe.reset_path(); + Point last_post_before_retract = this->last_pos(); gcode += this->retract(); // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 60c1dd9e1..d13ace30b 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -156,6 +156,7 @@ public: const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } + const GCodeWriter& writer() const { return m_writer; } PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } // Process a template through the placeholder parser, collect error messages to be reported diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index c5e43a65a..25fe631c2 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -115,6 +115,7 @@ static Matrix2d rotation_by_direction(const Point &direction) static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) { + assert(point_idx < polygon.size()); if (point != polygon.points[point_idx]) return polygon.points[point_idx]; @@ -137,8 +138,9 @@ static Vec2d three_points_inward_normal(const Point &left, const Point &middle, normal_2.normalize(); return (normal_1 + normal_2).normalized(); -}; +} +// Compute normal of the polygon's vertex in an inward direction static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); @@ -149,12 +151,13 @@ static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size return three_points_inward_normal(left, middle, right); } -// Compute offset of polygon's in a direction inward normal +// Compute offset of point_idx of the polygon in a direction of inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); } +// Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) { const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); @@ -243,6 +246,15 @@ static coord_t get_perimeter_spacing_external(const Layer &layer) return perimeter_spacing; } +// Check if anyone of ExPolygons contains whole travel. +template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) +{ + for (const ExPolygon &ex_polygon : ex_polygons) + if (ex_polygon.contains(travel)) return true; + + return false; +} + ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) { const coord_t perimeter_spacing = get_perimeter_spacing(layer); @@ -321,6 +333,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) Polygons contours; Polygons holes; + contours.reserve(boundary.size()); for (ExPolygon &poly : boundary) { contours.emplace_back(poly.contour); append(holes, poly.holes); @@ -429,10 +442,11 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr return optimized_comb_path; } -Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end) +size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + Polyline *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -496,12 +510,12 @@ Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &bounda // Append the nearest intersection into the path size_t left_idx = intersection_first.line_idx; size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + // Offset of the polygon's point using get_middle_point_offset is used to simplify calculation of intersection between boundary result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); // Append the path around the border into the path - // Offset of the polygon's point is used to simplify calculation of intersection between boundary if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) @@ -524,10 +538,48 @@ Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &bounda } result.append(end); - return simplify_travel(edge_grid, result); + if(!intersections.empty()) { + result = simplify_travel(edge_grid, result); + } + + append(result_out->points, result.points); + return intersections.size(); } -Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) +bool AvoidCrossingPerimeters2::needs_wipe(const GCode & gcodegen, + const Line & original_travel, + const Polyline &result_travel, + const size_t intersection_count) +{ + bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; + bool wipe_needed = false; + + // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely + // outside the object. + if (intersection_count > 0) { + // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. + // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. + if (z_lift_enabled) { + if (any_expolygon_contains(m_slice, original_travel)) { + // Check if original_travel and are not same result_travel + if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { + wipe_needed = false; + } else { + wipe_needed = !any_expolygon_contains(m_slice, result_travel); + } + } else { + wipe_needed = true; + } + } else { + wipe_needed = !any_expolygon_contains(m_slice, result_travel); + } + } + + return wipe_needed; +} + +// Plan travel, which avoids perimeter crossings by following the boundaries of the layer. +Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. @@ -536,14 +588,16 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point Point start = gcodegen.last_pos() + scaled_origin; Point end = point + scaled_origin; Polyline result; + size_t travel_intersection_count = 0; if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result = Polyline({start, end}); + result = Polyline({start, end}); + travel_intersection_count = 0; } else { auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); if (use_external) - result = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped); + travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, &result); else - result = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped); + travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, &result); } result.points.front() = start; @@ -554,33 +608,32 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { result = Polyline({start, end}); } - if (use_external) + if (use_external) { result.translate(-scaled_origin); + *could_be_wipe_disabled = false; + } else + *could_be_wipe_disabled = !needs_wipe(gcodegen, travel, result, travel_intersection_count); + return result; } void AvoidCrossingPerimeters2::init_layer(const Layer &layer) { + m_slice.clear(); m_boundaries.clear(); m_boundaries_external.clear(); - ExPolygons boundaries = get_boundary(layer); - ExPolygons boundaries_external = get_boundary_external(layer); + for (const LayerRegion *layer_region : layer.regions()) + append(m_slice, (ExPolygons) layer_region->slices); - m_bbox = get_extents(boundaries); + m_boundaries = to_polygons(get_boundary(layer)); + m_boundaries_external = to_polygons(get_boundary_external(layer)); + + m_bbox = get_extents(m_boundaries); m_bbox.offset(SCALED_EPSILON); - m_bbox_external = get_extents(boundaries_external); + m_bbox_external = get_extents(m_boundaries_external); m_bbox_external.offset(SCALED_EPSILON); - for (const ExPolygon &ex_poly : boundaries) { - m_boundaries.emplace_back(ex_poly.contour); - append(m_boundaries, ex_poly.holes); - } - for (const ExPolygon &ex_poly : boundaries_external) { - m_boundaries_external.emplace_back(ex_poly.contour); - append(m_boundaries_external, ex_poly.holes); - } - m_grid.set_bbox(m_bbox); m_grid.create(m_boundaries, scale_(1.)); m_grid_external.set_bbox(m_bbox_external); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index bc1f79654..e288a5b06 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -37,11 +37,17 @@ public: m_external_mp.reset(); m_layer_mp.reset(); } - void init_external_mp(const Print &print); - void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } + virtual void init_external_mp(const Print &print); + virtual void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } virtual Polyline travel_to(const GCode &gcodegen, const Point &point); + virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) + { + *could_be_wipe_disabled = true; + return this->travel_to(gcodegen, point); + } + protected: // For initializing the regions to avoid. static Polygons collect_contours_all_layers(const PrintObjectPtrs &objects); @@ -55,9 +61,13 @@ class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters protected: struct Intersection { + // Index of the polygon containing this point of intersection. size_t border_idx; + // Index of the line on the polygon containing this point of intersection. size_t line_idx; + // Point of intersection projected on the travel path. Point point_transformed; + // Point of intersection. Point point; Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) @@ -78,11 +88,19 @@ private: static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); - static Polyline avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end); + static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end, Polyline *result_out); + bool needs_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); + + // Slice of layer with elephant foot compensation + ExPolygons m_slice; + // Collection of boundaries used for detection of crossing perimetrs for travels inside object Polygons m_boundaries; + // Collection of boundaries used for detection of crossing perimetrs for travels outside object Polygons m_boundaries_external; + // Bounding box of m_boundaries BoundingBox m_bbox; + // Bounding box of m_boundaries_external BoundingBox m_bbox_external; EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid_external; @@ -92,7 +110,17 @@ public: virtual ~AvoidCrossingPerimeters2() = default; - virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; + // Used for disabling unnecessary calling collect_contours_all_layers + virtual void init_external_mp(const Print &print) override {}; + virtual void init_layer_mp(const ExPolygons &islands) override {}; + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override + { + bool could_be_wipe_disabled; + return this->travel_to(gcodegen, point, &could_be_wipe_disabled); + } + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *needs_wipe) override; void init_layer(const Layer &layer); }; From c00c7eaed36af04c4bf4d9dad9833116fcdad759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 28 Oct 2020 01:52:50 +0100 Subject: [PATCH 019/225] Rework of outer borders to reduce unnecessary detours along the border. The resulting path now contains all intersection with borders, which allows eliminating more unnecessary detours and more simplify the path. --- .../GCode/AvoidCrossingPerimeters.cpp | 204 ++++++++++++------ .../GCode/AvoidCrossingPerimeters.hpp | 2 +- 2 files changed, 135 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 25fe631c2..e05b5b0ff 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -255,10 +255,43 @@ template static bool any_expolygon_contains(const ExPolygons &ex_polygo return false; } +static std::pair split_expolygon(const ExPolygons &ex_polygons) +{ + Polygons contours, holes; + contours.reserve(ex_polygons.size()); + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), 0, + [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); + for (const ExPolygon &ex_poly : ex_polygons) { + contours.emplace_back(ex_poly.contour); + append(holes, ex_poly.holes); + } + return std::make_pair(std::move(contours), std::move(holes)); +} + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const Polyline &result_travel, + const std::vector &intersections, + const std::string &path) +{ + BoundingBox bbox = get_extents(boundary); + ::Slic3r::SVG svg(path, bbox); + svg.draw_outline(boundary, "green"); + svg.draw(original_travel, "blue"); + svg.draw(result_travel, "red"); + svg.draw(original_travel.a, "black"); + svg.draw(original_travel.b, "grey"); + + for (const AvoidCrossingPerimeters2::Intersection &intersection : intersections) + svg.draw(intersection.point, "lightseagreen"); +} +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) { const coord_t perimeter_spacing = get_perimeter_spacing(layer); - const coord_t offset = perimeter_spacing / 2; + const coord_t perimeter_offset = perimeter_spacing / 2; size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) polygons_count += layer_region->slices.surfaces.size(); @@ -269,25 +302,31 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); boundary = union_ex(boundary); - ExPolygons perimeter_boundary = offset_ex(boundary, -offset); - ExPolygons final_boundary; + ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); + ExPolygons result_boundary; if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, the boundary of slice is used instead. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), - offset + SCALED_EPSILON); - perimeter_boundary = offset_ex(perimeter_boundary, offset); + // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, + offset_ex(perimeter_boundary, perimeter_offset + SCALED_EPSILON / 2)), + perimeter_offset + SCALED_EPSILON); + perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); - perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); + perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); + // By calling intersection_ex some artifacts arose by previous operations are removed. + result_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary)); } else { - final_boundary = std::move(perimeter_boundary); + result_boundary = std::move(perimeter_boundary); } + auto [contours, holes] = split_expolygon(boundary); // Add an outer boundary to avoid crossing perimeters from supports - ExPolygons outer_boundary = diff_ex(offset_ex(boundary, 2 * perimeter_spacing), offset_ex(boundary, 2 * perimeter_spacing - offset)); - final_boundary.reserve(final_boundary.size() + outer_boundary.size()); - final_boundary.insert(final_boundary.begin(), outer_boundary.begin(), outer_boundary.end()); - final_boundary = union_ex(final_boundary); + ExPolygons outer_boundary = union_ex( + diff(static_cast(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); + ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); // Collect all top layers that will not be crossed. polygons_count = 0; @@ -303,15 +342,18 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset)); + return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); } - return final_boundary; + return result_boundary; } ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) { - ExPolygons boundary; + const coord_t perimeter_spacing = get_perimeter_spacing_external(layer); + const coord_t perimeter_offset = perimeter_spacing / 2; + ExPolygons boundary; + // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". for (const PrintObject *object : layer.object()->print()->objects()) { ExPolygons polygons_per_obj; for (Layer *l : object->layers()) @@ -327,24 +369,19 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); } } - - const coord_t perimeter_spacing = get_perimeter_spacing_external(layer); - const coord_t perimeter_offset = perimeter_spacing / 2; - - Polygons contours; - Polygons holes; - contours.reserve(boundary.size()); - for (ExPolygon &poly : boundary) { - contours.emplace_back(poly.contour); - append(holes, poly.holes); - } - - ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset))); + boundary = union_ex(boundary); + auto [contours, holes] = split_expolygon(boundary); + // Polygons in which is possible traveling without crossing perimeters of another object. + // A convex hull allows removing unnecessary detour caused by following the boundary of the object. + ExPolygons result_boundary = union_ex( + diff(static_cast(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); - final_boundary.reserve(final_boundary.size() + holes_boundary.size()); - final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - final_boundary = union_ex(final_boundary); - return final_boundary; + result_boundary.reserve(result_boundary.size() + holes_boundary.size()); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); + return result_boundary; } // Returns a direction of the shortest path along the polygon boundary @@ -409,12 +446,12 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr bool intersect = false; } visitor(edge_grid); - Polyline optimized_comb_path; - optimized_comb_path.points.reserve(travel.points.size()); - optimized_comb_path.points.emplace_back(travel.points.front()); + Polyline simplified_path; + simplified_path.points.reserve(travel.points.size()); + simplified_path.points.emplace_back(travel.points.front()); // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { const Point ¤t_point = travel.points[point_idx - 1]; Point next = travel.points[point_idx]; @@ -436,10 +473,10 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr } } - optimized_comb_path.append(next); + simplified_path.append(next); } - return optimized_comb_path; + return simplified_path; } size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, @@ -502,45 +539,72 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari Polyline result; result.append(start); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + // The entry point to the boundary polygon const Intersection &intersection_first = *it_first; - for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { + // Skip the it_first from the search for the farthest exit point from the boundary polygon + auto it_last_item = std::make_reverse_iterator(it_first) - 1; + // Search for the farthest intersection different from it_first but with the same border_idx + auto it_second_r = std::find_if(intersections.rbegin(), it_last_item, [&intersection_first](const Intersection &intersection) { + return intersection_first.border_idx == intersection.border_idx; + }); + + // Append the first intersection into the path + size_t left_idx = intersection_first.line_idx; + size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the + // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the + // appended point will be inside the polygon and not on the polygon border. + result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); + + // Check if intersection line also exit the boundary polygon + if (it_second_r != it_last_item) { + // Transform reverse iterator to forward + auto it_second = (it_second_r.base() - 1); + // The exit point from the boundary polygon const Intersection &intersection_second = *it_second; - if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = boundaries[intersection_first.border_idx].lines(); - // Append the nearest intersection into the path - size_t left_idx = intersection_first.line_idx; - size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); - // Offset of the polygon's point using get_middle_point_offset is used to simplify calculation of intersection between boundary - result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); + Lines border_lines = boundaries[intersection_first.border_idx].lines(); - Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, - intersection_first.point, intersection_second.point); - // Append the path around the border into the path - if (shortest_direction == Direction::Forward) - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); - else - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); + Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); + // Append the path around the border into the path + if (shortest_direction == Direction::Forward) + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); + else + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) + result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); - // Append the farthest intersection into the path - left_idx = intersection_second.line_idx; - right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); - // Skip intersections in between - it_first = (it_second - 1); - break; - } + // Append the farthest intersection into the path + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); + // Skip intersections in between + it_first = it_second; } } result.append(end); - if(!intersections.empty()) { - result = simplify_travel(edge_grid, result); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, travel_line_orig, result, intersections, + debug_out_path("AvoidCrossingPerimeters-initial-%d.svg", iRun++)); } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + if(!intersections.empty()) + result = simplify_travel(edge_grid, result); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, travel_line_orig, result, intersections, + debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ append(result_out->points, result.points); return intersections.size(); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index e288a5b06..01f844de4 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -58,7 +58,7 @@ protected: class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters { -protected: +public: struct Intersection { // Index of the polygon containing this point of intersection. From 266e6dee5d1f0adfaed747e301ed3e8955af83b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 28 Oct 2020 12:57:45 +0100 Subject: [PATCH 020/225] Fix compiler warnings --- .../GCode/AvoidCrossingPerimeters.cpp | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index e05b5b0ff..9a2324395 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -119,7 +119,7 @@ static Point find_first_different_vertex(const Polygon &polygon, const size_t po if (point != polygon.points[point_idx]) return polygon.points[point_idx]; - int line_idx = point_idx; + int line_idx = int(point_idx); if (forward) for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); else @@ -158,7 +158,7 @@ static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t poin } // Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). -static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) +static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset) { const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); @@ -203,33 +203,33 @@ static std::pair clamp_endpoints_by_bounding_box(const BoundingBox return std::make_pair(start_clamped, end_clamped); } -static inline coord_t get_default_perimeter_spacing(const Print &print) +static inline float get_default_perimeter_spacing(const Print &print) { const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; - return scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end())); + return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); } -static coord_t get_perimeter_spacing(const Layer &layer) +static float get_perimeter_spacing(const Layer &layer) { - size_t regions_count = 0; - coord_t perimeter_spacing = 0; + size_t regions_count = 0; + float perimeter_spacing = 0.f; for (const LayerRegion *layer_region : layer.regions()) { perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++regions_count; } - assert(perimeter_spacing >= 0); + assert(perimeter_spacing >= 0.f); if (regions_count != 0) - perimeter_spacing /= regions_count; + perimeter_spacing /= float(regions_count); else perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); return perimeter_spacing; } -static coord_t get_perimeter_spacing_external(const Layer &layer) +static float get_perimeter_spacing_external(const Layer &layer) { - size_t regions_count = 0; - coord_t perimeter_spacing = 0; + size_t regions_count = 0; + float perimeter_spacing = 0.f; for (const PrintObject *object : layer.object()->print()->objects()) for (Layer *l : object->layers()) if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) @@ -238,9 +238,9 @@ static coord_t get_perimeter_spacing_external(const Layer &layer) ++regions_count; } - assert(perimeter_spacing >= 0); + assert(perimeter_spacing >= 0.f); if (regions_count != 0) - perimeter_spacing /= regions_count; + perimeter_spacing /= float(regions_count); else perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); return perimeter_spacing; @@ -290,9 +290,9 @@ static void export_travel_to_svg(const Polygons ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) { - const coord_t perimeter_spacing = get_perimeter_spacing(layer); - const coord_t perimeter_offset = perimeter_spacing / 2; - size_t polygons_count = 0; + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) polygons_count += layer_region->slices.surfaces.size(); @@ -307,8 +307,8 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) if (perimeter_boundary.size() != boundary.size()) { // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, - offset_ex(perimeter_boundary, perimeter_offset + SCALED_EPSILON / 2)), - perimeter_offset + SCALED_EPSILON); + offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), + perimeter_offset + float(SCALED_EPSILON)); perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); @@ -321,7 +321,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) auto [contours, holes] = split_expolygon(boundary); // Add an outer boundary to avoid crossing perimeters from supports ExPolygons outer_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))), + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), offset(contours, perimeter_spacing + perimeter_offset))); result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); @@ -350,9 +350,9 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) { - const coord_t perimeter_spacing = get_perimeter_spacing_external(layer); - const coord_t perimeter_offset = perimeter_spacing / 2; - ExPolygons boundary; + const float perimeter_spacing = get_perimeter_spacing_external(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + ExPolygons boundary; // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". for (const PrintObject *object : layer.object()->print()->objects()) { ExPolygons polygons_per_obj; @@ -374,7 +374,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) // Polygons in which is possible traveling without crossing perimeters of another object. // A convex hull allows removing unnecessary detour caused by following the boundary of the object. ExPolygons result_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))), + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), offset(contours, perimeter_spacing + perimeter_offset))); // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); @@ -398,7 +398,7 @@ AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direc if (index >= int(lines.size())) index = 0; else if (index < 0) - index = lines.size() - 1; + index = int(lines.size()) - 1; return index; }; @@ -554,7 +554,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the // appended point will be inside the polygon and not on the polygon border. - result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); + result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON))); // Check if intersection line also exit the boundary polygon if (it_second_r != it_last_item) { @@ -567,19 +567,19 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); // Append the path around the border into the path if (shortest_direction == Direction::Forward) - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON))); else - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); + result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON))); // Append the farthest intersection into the path left_idx = intersection_second.line_idx; right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); + result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON))); // Skip intersections in between it_first = it_second; } @@ -699,9 +699,9 @@ void AvoidCrossingPerimeters2::init_layer(const Layer &layer) m_bbox_external.offset(SCALED_EPSILON); m_grid.set_bbox(m_bbox); - m_grid.create(m_boundaries, scale_(1.)); + m_grid.create(m_boundaries, coord_t(scale_(1.))); m_grid_external.set_bbox(m_bbox_external); - m_grid_external.create(m_boundaries_external, scale_(1.)); + m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); } } // namespace Slic3r From 3db66af71686e08379e94201034685d05279893a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 28 Oct 2020 13:21:06 +0100 Subject: [PATCH 021/225] Fix another compiler warning --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 9a2324395..07551d055 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -259,7 +259,7 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo { Polygons contours, holes; contours.reserve(ex_polygons.size()); - holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), 0, + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); for (const ExPolygon &ex_poly : ex_polygons) { contours.emplace_back(ex_poly.contour); From 9936b8e34e280216543c3ce849816eb1cdc10b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 15 Nov 2020 19:46:17 +0100 Subject: [PATCH 022/225] Add missing includes --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 14 ++++++++++---- src/libslic3r/GCode/AvoidCrossingPerimeters.hpp | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 07551d055..5e3a091a1 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -12,6 +12,11 @@ #include "AvoidCrossingPerimeters.hpp" #include +#include +#include + +#include +#include namespace Slic3r { @@ -457,7 +462,7 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr visitor.pt_current = ¤t_point; - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { if (travel.points[point_idx_2] == current_point) { next = travel.points[point_idx_2]; point_idx = point_idx_2; @@ -610,7 +615,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari return intersections.size(); } -bool AvoidCrossingPerimeters2::needs_wipe(const GCode & gcodegen, +bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen, const Line & original_travel, const Polyline &result_travel, const size_t intersection_count) @@ -625,7 +630,8 @@ bool AvoidCrossingPerimeters2::needs_wipe(const GCode & gcodegen, // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. if (z_lift_enabled) { if (any_expolygon_contains(m_slice, original_travel)) { - // Check if original_travel and are not same result_travel + // Check if original_travel and result_travel are not same. + // If both are the same, then it is possible to skip testing of result_travel if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { wipe_needed = false; } else { @@ -676,7 +682,7 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point result.translate(-scaled_origin); *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !needs_wipe(gcodegen, travel, result, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, travel, result, travel_intersection_count); return result; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 01f844de4..c73fda82d 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -90,7 +90,7 @@ private: static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end, Polyline *result_out); - bool needs_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); + bool need_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); // Slice of layer with elephant foot compensation ExPolygons m_slice; @@ -120,7 +120,7 @@ public: return this->travel_to(gcodegen, point, &could_be_wipe_disabled); } - virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *needs_wipe) override; + virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) override; void init_layer(const Layer &layer); }; From c702b3b71dfc94d71dc642f37d9b8b9e1c005b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 16 Nov 2020 13:43:15 +0100 Subject: [PATCH 023/225] Add heuristics for removing unnecessary detours --- .../GCode/AvoidCrossingPerimeters.cpp | 233 ++++++++++++------ .../GCode/AvoidCrossingPerimeters.hpp | 71 +++++- 2 files changed, 228 insertions(+), 76 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 5e3a091a1..4e45ed28b 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -9,6 +9,7 @@ #include "../Polygon.hpp" #include "../ExPolygon.hpp" #include "../ClipperUtils.hpp" +#include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" #include @@ -273,6 +274,23 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo return std::make_pair(std::move(contours), std::move(holes)); } +static Polyline to_polyline(const std::vector &travel) +{ + Polyline result; + result.points.reserve(travel.size()); + for (const AvoidCrossingPerimeters2::TravelPoint &t_point : travel) + result.append(t_point.point); + return result; +} + +static double travel_length(const std::vector &travel) { + double total_length = 0; + for (size_t idx = 1; idx < travel.size(); ++idx) + total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); + + return total_length; +} + #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT static void export_travel_to_svg(const Polygons &boundary, const Line &original_travel, @@ -291,6 +309,15 @@ static void export_travel_to_svg(const Polygons for (const AvoidCrossingPerimeters2::Intersection &intersection : intersections) svg.draw(intersection.point, "lightseagreen"); } + +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const std::vector &result_travel, + const std::vector &intersections, + const std::string &path) +{ + export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); +} #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) @@ -420,7 +447,10 @@ AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direc return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel) +std::vector AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics) { struct Visitor { @@ -451,44 +481,131 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr bool intersect = false; } visitor(edge_grid); - Polyline simplified_path; - simplified_path.points.reserve(travel.points.size()); - simplified_path.points.emplace_back(travel.points.front()); + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); // Try to skip some points in the path. for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - const Point ¤t_point = travel.points[point_idx - 1]; - Point next = travel.points[point_idx]; + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; visitor.pt_current = ¤t_point; for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel.points[point_idx_2] == current_point) { - next = travel.points[point_idx_2]; + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; point_idx = point_idx_2; continue; } - visitor.pt_next = &travel.points[point_idx_2]; + visitor.pt_next = &travel[point_idx_2].point; edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); // Check if deleting point causes crossing a boundary if (!visitor.intersect) { - next = travel.points[point_idx_2]; + next = travel[point_idx_2]; point_idx = point_idx_2; } } - simplified_path.append(next); + simplified_path.emplace_back(next); + } + + if(use_heuristics) { + simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); + std::reverse(simplified_path.begin(),simplified_path.end()); + simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); + std::reverse(simplified_path.begin(),simplified_path.end()); } return simplified_path; } -size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - Polyline *result_out) +std::vector AvoidCrossingPerimeters2::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries) +{ + std::vector simplified_path; + std::vector intersections; + AllIntersectionsVisitor visitor(edge_grid, intersections); + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + // Skip all indexes on the same polygon + while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) { + simplified_path.emplace_back(travel[point_idx]); + point_idx++; + } + + if (point_idx < travel.size()) { + const TravelPoint ¤t = travel[point_idx - 1]; + const TravelPoint &next = travel[point_idx]; + TravelPoint new_next = next; + size_t new_point_idx = point_idx; + double path_length = (next.point - current.point).cast().norm(); + double new_path_shorter_by = 0.; + size_t border_idx_change_count = 0; + std::vector shortcut; + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + const TravelPoint &possible_new_next = travel[point_idx_2]; + if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx) + border_idx_change_count++; + + if (border_idx_change_count >= 2) + break; + + path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast().norm(); + double shortcut_length = (possible_new_next.point - current.point).cast().norm(); + if ((path_length - shortcut_length) <= scale_(10.0)) + continue; + + intersections.clear(); + visitor.reset(); + visitor.travel_line.a = current.point; + visitor.travel_line.b = possible_new_next.point; + visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); + edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); + if (!intersections.empty()) { + std::sort(intersections.begin(), intersections.end()); + size_t last_border_idx_count = 0; + for (const Intersection &intersection : intersections) + if (intersection.border_idx == int(possible_new_next.border_idx)) + ++last_border_idx_count; + + if (last_border_idx_count > 0) + continue; + + std::vector possible_shortcut; + avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); + double shortcut_travel = travel_length(possible_shortcut); + if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { + new_path_shorter_by = path_length - shortcut_travel; + shortcut = possible_shortcut; + new_next = possible_new_next; + new_point_idx = point_idx_2; + } + } + } + + if (!shortcut.empty()) { + assert(shortcut.size() >= 2); + simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1); + point_idx = new_point_idx; + } + + simplified_path.emplace_back(new_next); + } + } + + return simplified_path; +} + +size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -499,50 +616,14 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari std::vector intersections; { - struct Visitor - { - Visitor(const EdgeGrid::Grid & grid, - std::vector &intersections, - const Matrix2d & transform_to_x_axis, - const Line & travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) - {} - - bool operator()(coord_t iy, coord_t ix) - { - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - - Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && - intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); - intersection_set.insert(*it_contour_and_segment); - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &intersections; - const Matrix2d &transform_to_x_axis; - const Line &travel_line; - std::unordered_set, boost::hash>> intersection_set; - } visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - + AllIntersectionsVisitor visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); edge_grid.visit_cells_intersecting_line(start, end, visitor); } std::sort(intersections.begin(), intersections.end()); - Polyline result; - result.append(start); + std::vector result; + result.push_back({start, -1}); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { // The entry point to the boundary polygon const Intersection &intersection_first = *it_first; @@ -559,7 +640,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the // appended point will be inside the polygon and not on the polygon border. - result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON))); + result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); // Check if intersection line also exit the boundary polygon if (it_second_r != it_last_item) { @@ -574,23 +655,23 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari if (shortest_direction == Direction::Forward) for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON))); + result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); else for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON))); + result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); // Append the farthest intersection into the path left_idx = intersection_second.line_idx; right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON))); + result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx)}); // Skip intersections in between it_first = it_second; } } - result.append(end); + result.push_back({end, -1}); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { @@ -601,7 +682,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ if(!intersections.empty()) - result = simplify_travel(edge_grid, result); + result = simplify_travel(edge_grid, result, boundaries, use_heuristics); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { @@ -611,7 +692,8 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - append(result_out->points, result.points); + result_out->reserve(result_out->size() + result.size()); + result_out->insert(result_out->end(), result.begin(), result.end()); return intersections.size(); } @@ -657,34 +739,37 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); Point start = gcodegen.last_pos() + scaled_origin; Point end = point + scaled_origin; - Polyline result; + Polyline result_pl; size_t travel_intersection_count = 0; if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result = Polyline({start, end}); + result_pl = Polyline({start, end}); travel_intersection_count = 0; } else { + std::vector result; auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); if (use_external) - travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, &result); + travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, false, &result); else - travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, &result); + travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, false, &result); + + result_pl = to_polyline(result); } - result.points.front() = start; - result.points.back() = end; + result_pl.points.front() = start; + result_pl.points.back() = end; Line travel(start, end); double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); - if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { - result = Polyline({start, end}); + if ((max_detour_length > 0) && ((result_pl.length() - travel.length()) > max_detour_length)) { + result_pl = Polyline({start, end}); } if (use_external) { - result.translate(-scaled_origin); + result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !need_wipe(gcodegen, travel, result, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, travel, result_pl, travel_intersection_count); - return result; + return result_pl; } void AvoidCrossingPerimeters2::init_layer(const Layer &layer) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index c73fda82d..f1a5324f4 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -5,6 +5,9 @@ #include "../ExPolygon.hpp" #include "../EdgeGrid.hpp" +#include +#include + namespace Slic3r { // Forward declarations. @@ -76,6 +79,58 @@ public: inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } }; + struct TravelPoint + { + Point point; + // Index of the polygon containing this point. A negative value indicates that the point is not on any border + int border_idx; + }; + + struct AllIntersectionsVisitor + { + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) + : grid(grid), intersections(intersections) + {} + + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, + std::vector &intersections, + const Matrix2d &transform_to_x_axis, + const Line &travel_line) + : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + {} + + void reset() { + intersection_set.clear(); + } + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + + Point intersection_point; + if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, + (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + std::vector &intersections; + Matrix2d transform_to_x_axis; + Line travel_line; + std::unordered_set, boost::hash>> intersection_set; + }; + enum class Direction { Forward, Backward }; private: @@ -86,9 +141,21 @@ private: static Direction get_shortest_direction( const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); - static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); + static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics); - static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end, Polyline *result_out); + static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries); + + static size_t avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out); bool need_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); From 49ce613be7b64426b29ff1cbca6173a29b8da94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 16 Nov 2020 14:37:42 +0100 Subject: [PATCH 024/225] Enable previous heuristics which was disabled by mistake --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 4e45ed28b..4aafa77fb 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -569,7 +569,7 @@ std::vector AvoidCrossingPerimeters2::sim std::sort(intersections.begin(), intersections.end()); size_t last_border_idx_count = 0; for (const Intersection &intersection : intersections) - if (intersection.border_idx == int(possible_new_next.border_idx)) + if (int(intersection.border_idx) == possible_new_next.border_idx) ++last_border_idx_count; if (last_border_idx_count > 0) @@ -748,9 +748,9 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point std::vector result; auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); if (use_external) - travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, false, &result); + travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); else - travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, false, &result); + travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); result_pl = to_polyline(result); } From 04c2fde671772600b98e094f9714acc5190b9e4c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 09:33:10 +0100 Subject: [PATCH 025/225] Removed the old motion planner. --- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/GCode.cpp | 12 +- src/libslic3r/GCode.hpp | 3 +- .../GCode/AvoidCrossingPerimeters.cpp | 169 ++------ .../GCode/AvoidCrossingPerimeters.hpp | 86 ++--- src/libslic3r/MotionPlanner.cpp | 363 ------------------ src/libslic3r/MotionPlanner.hpp | 91 ----- xs/src/perlglue.cpp | 3 - xs/t/18_motionplanner.t | 92 ----- xs/xsp/MotionPlanner.xsp | 15 - xs/xsp/my.map | 12 - xs/xsp/typemap.xspt | 6 - 12 files changed, 67 insertions(+), 787 deletions(-) delete mode 100644 src/libslic3r/MotionPlanner.cpp delete mode 100644 src/libslic3r/MotionPlanner.hpp delete mode 100644 xs/t/18_motionplanner.t delete mode 100644 xs/xsp/MotionPlanner.xsp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 93f947545..4d65de81c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -128,8 +128,6 @@ add_library(libslic3r STATIC CustomGCode.hpp Arrange.hpp Arrange.cpp - MotionPlanner.cpp - MotionPlanner.hpp MultiPoint.cpp MultiPoint.hpp MutablePriorityQueue.hpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f1c8f5bbf..96eff24e7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1140,13 +1140,6 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Set other general things. _write(file, this->preamble()); - // Initialize a motion planner for object-to-object travel moves. - m_avoid_crossing_perimeters.reset(); - if (print.config().avoid_crossing_perimeters.value) { - m_avoid_crossing_perimeters.init_external_mp(print); - print.throw_if_canceled(); - } - // Calculate wiping points if needed DoExport::init_ooze_prevention(print, m_ooze_prevention); print.throw_if_canceled(); @@ -2054,11 +2047,8 @@ void GCode::process_layer( for (InstanceToPrint &instance_to_print : instances_to_print) { m_config.apply(instance_to_print.print_object.config(), true); m_layer = layers[instance_to_print.layer_id].layer(); - if (m_config.avoid_crossing_perimeters) { - m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->lslices, true)); + if (m_config.avoid_crossing_perimeters) m_avoid_crossing_perimeters.init_layer(*m_layer); - } - if (this->config().gcode_label_objects) gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; // When starting a new object, use the external motion planner for the first travel move. diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index d13ace30b..b21f4c97d 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -5,7 +5,6 @@ #include "ExPolygon.hpp" #include "GCodeWriter.hpp" #include "Layer.hpp" -#include "MotionPlanner.hpp" #include "Point.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" @@ -299,7 +298,7 @@ private: std::set m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; - AvoidCrossingPerimeters2 m_avoid_crossing_perimeters; + AvoidCrossingPerimeters m_avoid_crossing_perimeters; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 4aafa77fb..6a211e05b 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1,7 +1,5 @@ #include "../Layer.hpp" -#include "../MotionPlanner.hpp" #include "../GCode.hpp" -#include "../MotionPlanner.hpp" #include "../EdgeGrid.hpp" #include "../Geometry.hpp" #include "../ShortestPath.hpp" @@ -21,93 +19,6 @@ namespace Slic3r { -void AvoidCrossingPerimeters::init_external_mp(const Print& print) -{ - m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); -} - -// Plan a travel move while minimizing the number of perimeter crossings. -// point is in unscaled coordinates, in the coordinate system of the current active object -// (set by gcodegen.set_origin()). -Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point) -{ - // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). - // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; - Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> - shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); - if (use_external) - result.translate(-scaled_origin); - return result; -} - -// Collect outer contours of all objects over all layers. -// Discard objects only containing thin walls (offset would fail on an empty polygon). -// Used by avoid crossing perimeters feature. -Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) -{ - Polygons islands; - for (const PrintObject* object : objects) { - // Reducing all the object slices into the Z projection in a logarithimc fashion. - // First reduce to half the number of layers. - std::vector polygons_per_layer((object->layers().size() + 1) / 2); - tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), - [&object, &polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - const Layer* layer1 = object->layers()[i * 2]; - const Layer* layer2 = object->layers()[i * 2 + 1]; - Polygons polys; - polys.reserve(layer1->lslices.size() + layer2->lslices.size()); - for (const ExPolygon& expoly : layer1->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - for (const ExPolygon& expoly : layer2->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer[i] = union_(polys); - } - }); - if (object->layers().size() & 1) { - const Layer* layer = object->layers().back(); - Polygons polys; - polys.reserve(layer->lslices.size()); - for (const ExPolygon& expoly : layer->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer.back() = union_(polys); - } - // Now reduce down to a single layer. - size_t cnt = polygons_per_layer.size(); - while (cnt > 1) { - tbb::parallel_for(tbb::blocked_range(0, cnt / 2), - [&polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - Polygons polys; - polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); - polygons_append(polys, polygons_per_layer[i * 2]); - polygons_append(polys, polygons_per_layer[i * 2 + 1]); - polygons_per_layer[i * 2] = union_(polys); - } - }); - for (size_t i = 1; i < cnt / 2; ++i) - polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); - if (cnt & 1) - polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); - cnt = (cnt + 1) / 2; - } - // And collect copies of the objects. - for (const PrintInstance& instance : object->instances()) { - // All the layers were reduced to the 1st item of polygons_per_layer. - size_t i = islands.size(); - polygons_append(islands, polygons_per_layer.front()); - for (; i < islands.size(); ++i) - islands[i].translate(instance.shift); - } - } - return islands; -} - // Create a rotation matrix for projection on the given vector static Matrix2d rotation_by_direction(const Point &direction) { @@ -274,16 +185,16 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo return std::make_pair(std::move(contours), std::move(holes)); } -static Polyline to_polyline(const std::vector &travel) +static Polyline to_polyline(const std::vector &travel) { Polyline result; result.points.reserve(travel.size()); - for (const AvoidCrossingPerimeters2::TravelPoint &t_point : travel) + for (const AvoidCrossingPerimeters::TravelPoint &t_point : travel) result.append(t_point.point); return result; } -static double travel_length(const std::vector &travel) { +static double travel_length(const std::vector &travel) { double total_length = 0; for (size_t idx = 1; idx < travel.size(); ++idx) total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); @@ -292,11 +203,11 @@ static double travel_length(const std::vector &intersections, - const std::string &path) +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const Polyline &result_travel, + const std::vector &intersections, + const std::string &path) { BoundingBox bbox = get_extents(boundary); ::Slic3r::SVG svg(path, bbox); @@ -306,21 +217,21 @@ static void export_travel_to_svg(const Polygons svg.draw(original_travel.a, "black"); svg.draw(original_travel.b, "grey"); - for (const AvoidCrossingPerimeters2::Intersection &intersection : intersections) + for (const AvoidCrossingPerimeters::Intersection &intersection : intersections) svg.draw(intersection.point, "lightseagreen"); } -static void export_travel_to_svg(const Polygons &boundary, - const Line &original_travel, - const std::vector &result_travel, - const std::vector &intersections, - const std::string &path) +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const std::vector &result_travel, + const std::vector &intersections, + const std::string &path) { export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ -ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -380,7 +291,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) return result_boundary; } -ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) +ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing_external(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -417,11 +328,11 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) } // Returns a direction of the shortest path along the polygon boundary -AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) +AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) { double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); @@ -447,10 +358,10 @@ AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direc return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -std::vector AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) +std::vector AvoidCrossingPerimeters::simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics) { struct Visitor { @@ -521,9 +432,9 @@ std::vector AvoidCrossingPerimeters2::sim return simplified_path; } -std::vector AvoidCrossingPerimeters2::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries) +std::vector AvoidCrossingPerimeters::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries) { std::vector simplified_path; std::vector intersections; @@ -600,12 +511,12 @@ std::vector AvoidCrossingPerimeters2::sim return simplified_path; } -size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -697,10 +608,10 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boun return intersections.size(); } -bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen, - const Line & original_travel, - const Polyline &result_travel, - const size_t intersection_count) +bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen, + const Line & original_travel, + const Polyline &result_travel, + const size_t intersection_count) { bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; bool wipe_needed = false; @@ -731,7 +642,7 @@ bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen, } // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. -Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. @@ -772,7 +683,7 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point return result_pl; } -void AvoidCrossingPerimeters2::init_layer(const Layer &layer) +void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_slice.clear(); m_boundaries.clear(); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index f1a5324f4..0f99108f9 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -13,7 +13,6 @@ namespace Slic3r { // Forward declarations. class GCode; class Layer; -class MotionPlanner; class Point; class Print; class PrintObject; @@ -23,44 +22,6 @@ using PrintObjectPtrs = std::vector; class AvoidCrossingPerimeters { -public: - // this flag triggers the use of the external configuration space - bool use_external_mp; - bool use_external_mp_once; // just for the next travel move - - // this flag disables avoid_crossing_perimeters just for the next travel move - // we enable it by default for the first travel move in print - bool disable_once; - - AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} - virtual ~AvoidCrossingPerimeters() = default; - - void reset() - { - m_external_mp.reset(); - m_layer_mp.reset(); - } - virtual void init_external_mp(const Print &print); - virtual void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point); - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) - { - *could_be_wipe_disabled = true; - return this->travel_to(gcodegen, point); - } - -protected: - // For initializing the regions to avoid. - static Polygons collect_contours_all_layers(const PrintObjectPtrs &objects); - - std::unique_ptr m_external_mp; - std::unique_ptr m_layer_mp; -}; - -class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters -{ public: struct Intersection { @@ -88,14 +49,14 @@ public: struct AllIntersectionsVisitor { - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) : grid(grid), intersections(intersections) {} - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, - std::vector &intersections, - const Matrix2d &transform_to_x_axis, - const Line &travel_line) + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, + std::vector &intersections, + const Matrix2d &transform_to_x_axis, + const Line &travel_line) : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) {} @@ -125,7 +86,7 @@ public: } const EdgeGrid::Grid &grid; - std::vector &intersections; + std::vector &intersections; Matrix2d transform_to_x_axis; Line travel_line; std::unordered_set, boost::hash>> intersection_set; @@ -141,14 +102,14 @@ private: static Direction get_shortest_direction( const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); - static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics); + static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics); - static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries); + static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries); static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, @@ -173,24 +134,27 @@ private: EdgeGrid::Grid m_grid_external; public: - AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} + // this flag triggers the use of the external configuration space + bool use_external_mp { false }; + // just for the next travel move + bool use_external_mp_once { false }; + // this flag disables avoid_crossing_perimeters just for the next travel move + // we enable it by default for the first travel move in print + bool disable_once { true }; - virtual ~AvoidCrossingPerimeters2() = default; + AvoidCrossingPerimeters() = default; - // Used for disabling unnecessary calling collect_contours_all_layers - virtual void init_external_mp(const Print &print) override {}; - virtual void init_layer_mp(const ExPolygons &islands) override {}; - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override + Polyline travel_to(const GCode& gcodegen, const Point& point) { bool could_be_wipe_disabled; return this->travel_to(gcodegen, point, &could_be_wipe_disabled); } - virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) override; + Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); void init_layer(const Layer &layer); }; + } // namespace Slic3r -#endif // slic3r_AvoidCrossingPerimeters_hpp_ \ No newline at end of file +#endif // slic3r_AvoidCrossingPerimeters_hpp_ diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp deleted file mode 100644 index ae50df8f4..000000000 --- a/src/libslic3r/MotionPlanner.cpp +++ /dev/null @@ -1,363 +0,0 @@ -#include "BoundingBox.hpp" -#include "MotionPlanner.hpp" -#include "MutablePriorityQueue.hpp" -#include "Utils.hpp" - -#include // for numeric_limits -#include - -#define BOOST_VORONOI_USE_GMP 1 -#include "boost/polygon/voronoi.hpp" -using boost::polygon::voronoi_builder; -using boost::polygon::voronoi_diagram; - -namespace Slic3r { - -MotionPlanner::MotionPlanner(const ExPolygons &islands) : m_initialized(false) -{ - ExPolygons expp; - for (const ExPolygon &island : islands) { - island.simplify(SCALED_EPSILON, &expp); - for (ExPolygon &island : expp) - m_islands.emplace_back(MotionPlannerEnv(island)); - expp.clear(); - } -} - -void MotionPlanner::initialize() -{ - // prevent initialization of empty BoundingBox - if (m_initialized || m_islands.empty()) - return; - - // loop through islands in order to create inner expolygons and collect their contours. - Polygons outer_holes; - for (MotionPlannerEnv &island : m_islands) { - // Generate the internal env boundaries by shrinking the island - // we'll use these inner rings for motion planning (endpoints of the Voronoi-based - // graph, visibility check) in order to avoid moving too close to the boundaries. - island.m_env = ExPolygonCollection(offset_ex(island.m_island, -MP_INNER_MARGIN)); - // Island contours are holes of our external environment. - outer_holes.push_back(island.m_island.contour); - } - - // Generate a box contour around everyting. - Polygons contour = offset(get_extents(outer_holes).polygon(), +MP_OUTER_MARGIN*2); - assert(contour.size() == 1); - // make expolygon for outer environment - ExPolygons outer = diff_ex(contour, outer_holes); - assert(outer.size() == 1); - // If some of the islands are nested, then the 0th contour is the outer contour due to the order of conversion - // from Clipper data structure into the Slic3r expolygons inside diff_ex(). - m_outer = MotionPlannerEnv(outer.front()); - m_outer.m_env = ExPolygonCollection(diff_ex(contour, offset(outer_holes, +MP_OUTER_MARGIN))); - m_graphs.resize(m_islands.size() + 1); - m_initialized = true; -} - -Polyline MotionPlanner::shortest_path(const Point &from, const Point &to) -{ - // If we have an empty configuration space, return a straight move. - if (m_islands.empty()) - return Polyline(from, to); - - // Are both points in the same island? - int island_idx_from = -1; - int island_idx_to = -1; - int island_idx = -1; - for (MotionPlannerEnv &island : m_islands) { - int idx = &island - m_islands.data(); - if (island.island_contains(from)) - island_idx_from = idx; - if (island.island_contains(to)) - island_idx_to = idx; - if (island_idx_from == idx && island_idx_to == idx) { - // Since both points are in the same island, is a direct move possible? - // If so, we avoid generating the visibility environment. - if (island.m_island.contains(Line(from, to))) - return Polyline(from, to); - // Both points are inside a single island, but the straight line crosses the island boundary. - island_idx = idx; - break; - } - } - - // lazy generation of configuration space. - this->initialize(); - - // Get environment. If the from / to points do not share an island, then they cross an open space, - // therefore island_idx == -1 and env will be set to the environment of the empty space. - const MotionPlannerEnv &env = this->get_env(island_idx); - if (env.m_env.expolygons.empty()) { - // if this environment is empty (probably because it's too small), perform straight move - // and avoid running the algorithms on empty dataset - return Polyline(from, to); - } - - // Now check whether points are inside the environment. - Point inner_from = from; - Point inner_to = to; - - if (island_idx == -1) { - // The end points do not share the same island. In that case some of the travel - // will be likely performed inside the empty space. - // TODO: instead of using the nearest_env_point() logic, we should - // create a temporary graph where we connect 'from' and 'to' to the - // nodes which don't require more than one crossing, and let Dijkstra - // figure out the entire path - this should also replace the call to - // find_node() below - if (island_idx_from != -1) - // The start point is inside some island. Find the closest point at the empty space to start from. - inner_from = env.nearest_env_point(from, to); - if (island_idx_to != -1) - // The start point is inside some island. Find the closest point at the empty space to start from. - inner_to = env.nearest_env_point(to, inner_from); - } - - // Perform a path search either in the open space, or in a common island of from/to. - const MotionPlannerGraph &graph = this->init_graph(island_idx); - // If no path exists without crossing perimeters, returns a straight segment. - Polyline polyline = graph.shortest_path(inner_from, inner_to); - polyline.points.insert(polyline.points.begin(), from); - polyline.points.emplace_back(to); - - { - // grow our environment slightly in order for simplify_by_visibility() - // to work best by considering moves on boundaries valid as well - ExPolygonCollection grown_env(offset_ex(env.m_env.expolygons, float(+SCALED_EPSILON))); - - if (island_idx == -1) { - /* If 'from' or 'to' are not inside our env, they were connected using the - nearest_env_point() search which maybe produce ugly paths since it does not - include the endpoint in the Dijkstra search; the simplify_by_visibility() - call below will not work in many cases where the endpoint is not contained in - grown_env (whose contour was arbitrarily constructed with MP_OUTER_MARGIN, - which may not be enough for, say, including a skirt point). So we prune - the extra points manually. */ - if (! grown_env.contains(from)) { - // delete second point while the line connecting first to third crosses the - // boundaries as many times as the current first to second - while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), (Polygons)grown_env).size() == 1) - polyline.points.erase(polyline.points.begin() + 1); - } - if (! grown_env.contains(to)) - while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), (Polygons)grown_env).size() == 1) - polyline.points.erase(polyline.points.end() - 2); - } - - // Perform some quick simplification (simplify_by_visibility() would make this - // unnecessary, but this is much faster) - polyline.simplify(MP_INNER_MARGIN/10); - - // remove unnecessary vertices - // Note: this is computationally intensive and does not look very necessary - // now that we prune the endpoints with the logic above, - // so we comment it for now until a good test case arises - //polyline.simplify_by_visibility(grown_env); - - /* - SVG svg("shortest_path.svg"); - svg.draw(grown_env.expolygons); - svg.arrows = false; - for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) { - Point a = graph->nodes[it - graph->adjacency_list.begin()]; - for (std::vector::const_iterator n = it->begin(); n != it->end(); ++n) { - Point b = graph->nodes[n->target]; - svg.draw(Line(a, b)); - } - } - svg.arrows = true; - svg.draw(from); - svg.draw(inner_from, "red"); - svg.draw(to); - svg.draw(inner_to, "red"); - svg.draw(polyline, "red"); - svg.Close(); - */ - } - - return polyline; -} - -const MotionPlannerGraph& MotionPlanner::init_graph(int island_idx) -{ - // 0th graph is the graph for m_outer. Other graphs are 1 indexed. - MotionPlannerGraph *graph = m_graphs[island_idx + 1].get(); - if (graph == nullptr) { - // If this graph doesn't exist, initialize it. - m_graphs[island_idx + 1] = make_unique(); - graph = m_graphs[island_idx + 1].get(); - - /* We don't add polygon boundaries as graph edges, because we'd need to connect - them to the Voronoi-generated edges by recognizing coinciding nodes. */ - - typedef voronoi_diagram VD; - VD vd; - // Mapping between Voronoi vertices and graph nodes. - std::map vd_vertices; - // get boundaries as lines - const MotionPlannerEnv &env = this->get_env(island_idx); - Lines lines = env.m_env.lines(); - boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd); - // traverse the Voronoi diagram and generate graph nodes and edges - for (const VD::edge_type &edge : vd.edges()) { - if (edge.is_infinite()) - continue; - const VD::vertex_type* v0 = edge.vertex0(); - const VD::vertex_type* v1 = edge.vertex1(); - Point p0(v0->x(), v0->y()); - Point p1(v1->x(), v1->y()); - // Insert only Voronoi edges fully contained in the island. - //FIXME This test has a terrible O(n^2) time complexity. - if (env.island_contains_b(p0) && env.island_contains_b(p1)) { - // Find v0 in the graph, allocate a new node if v0 does not exist in the graph yet. - auto i_v0 = vd_vertices.find(v0); - size_t v0_idx; - if (i_v0 == vd_vertices.end()) - vd_vertices[v0] = v0_idx = graph->add_node(p0); - else - v0_idx = i_v0->second; - // Find v1 in the graph, allocate a new node if v0 does not exist in the graph yet. - auto i_v1 = vd_vertices.find(v1); - size_t v1_idx; - if (i_v1 == vd_vertices.end()) - vd_vertices[v1] = v1_idx = graph->add_node(p1); - else - v1_idx = i_v1->second; - // Euclidean distance is used as weight for the graph edge - graph->add_edge(v0_idx, v1_idx, (p1 - p0).cast().norm()); - } - } - } - - return *graph; -} - -// Find a middle point on the path from start_point to end_point with the shortest path. -static inline size_t nearest_waypoint_index(const Point &start_point, const Points &middle_points, const Point &end_point) -{ - size_t idx = size_t(-1); - double dmin = std::numeric_limits::infinity(); - for (const Point &p : middle_points) { - double d = (p - start_point).cast().norm() + (end_point - p).cast().norm(); - if (d < dmin) { - idx = &p - middle_points.data(); - dmin = d; - if (dmin < EPSILON) - break; - } - } - return idx; -} - -Point MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const -{ - /* In order to ensure that the move between 'from' and the initial env point does - not violate any of the configuration space boundaries, we limit our search to - the points that satisfy this condition. */ - - /* Assume that this method is never called when 'env' contains 'from'; - so 'from' is either inside a hole or outside all contours */ - - // get the points of the hole containing 'from', if any - Points pp; - for (const ExPolygon &ex : m_env.expolygons) { - for (const Polygon &hole : ex.holes) - if (hole.contains(from)) - pp = hole; - if (! pp.empty()) - break; - } - - // If 'from' is not inside a hole, it's outside of all contours, so take all contours' points. - if (pp.empty()) - for (const ExPolygon &ex : m_env.expolygons) - append(pp, ex.contour.points); - - // Find the candidate result and check that it doesn't cross too many boundaries. - while (pp.size() > 1) { - // find the point in pp that is closest to both 'from' and 'to' - size_t result = nearest_waypoint_index(from, pp, to); - // as we assume 'from' is outside env, any node will require at least one crossing - if (intersection_ln(Line(from, pp[result]), m_island).size() > 1) { - // discard result - pp.erase(pp.begin() + result); - } else - return pp[result]; - } - - // if we're here, return last point if any (better than nothing) - // if we have no points at all, then we have an empty environment and we - // make this method behave as a no-op (we shouldn't get here by the way) - return pp.empty() ? from : pp.front(); -} - -// Add a new directed edge to the adjacency graph. -void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight) -{ - // Extend adjacency list until this start node. - if (m_adjacency_list.size() < from + 1) { - // Reserve in powers of two to avoid repeated reallocation. - m_adjacency_list.reserve(std::max(8, next_highest_power_of_2((uint32_t)(from + 1)))); - // Allocate new empty adjacency vectors. - m_adjacency_list.resize(from + 1); - } - m_adjacency_list[from].emplace_back(Neighbor(node_t(to), weight)); -} - -// Dijkstra's shortest path in a weighted graph from node_start to node_end. -// The returned path contains the end points. -// If no path exists from node_start to node_end, a straight segment is returned. -Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) const -{ - // This prevents a crash in case for some reason we got here with an empty adjacency list. - if (this->empty()) - return Polyline(); - - // Dijkstra algorithm, previous node of the current node 'u' in the shortest path towards node_start. - std::vector previous(m_adjacency_list.size(), -1); - std::vector distance(m_adjacency_list.size(), std::numeric_limits::infinity()); - std::vector map_node_to_queue_id(m_adjacency_list.size(), size_t(-1)); - distance[node_start] = 0.; - - auto queue = make_mutable_priority_queue( - [&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; }, - [&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; }); - queue.reserve(m_adjacency_list.size()); - for (size_t i = 0; i < m_adjacency_list.size(); ++ i) - queue.push(node_t(i)); - - while (! queue.empty()) { - // Get the next node with the lowest distance to node_start. - node_t u = node_t(queue.top()); - queue.pop(); - map_node_to_queue_id[u] = size_t(-1); - // Stop searching if we reached our destination. - if (size_t(u) == node_end) - break; - // Visit each edge starting at node u. - for (const Neighbor& neighbor : m_adjacency_list[u]) - if (map_node_to_queue_id[neighbor.target] != size_t(-1)) { - weight_t alt = distance[u] + neighbor.weight; - // If total distance through u is shorter than the previous - // distance (if any) between node_start and neighbor.target, replace it. - if (alt < distance[neighbor.target]) { - distance[neighbor.target] = alt; - previous[neighbor.target] = u; - queue.update(map_node_to_queue_id[neighbor.target]); - } - } - } - - // In case the end point was not reached, previous[node_end] contains -1 - // and a straight line from node_start to node_end is returned. - Polyline polyline; - polyline.points.reserve(m_adjacency_list.size()); - for (node_t vertex = node_t(node_end); vertex != -1; vertex = previous[vertex]) - polyline.points.emplace_back(m_nodes[vertex]); - polyline.points.emplace_back(m_nodes[node_start]); - polyline.reverse(); - return polyline; -} - -} diff --git a/src/libslic3r/MotionPlanner.hpp b/src/libslic3r/MotionPlanner.hpp deleted file mode 100644 index e912f2fb4..000000000 --- a/src/libslic3r/MotionPlanner.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef slic3r_MotionPlanner_hpp_ -#define slic3r_MotionPlanner_hpp_ - -#include "libslic3r.h" -#include "BoundingBox.hpp" -#include "ClipperUtils.hpp" -#include "ExPolygonCollection.hpp" -#include "Polyline.hpp" -#include -#include -#include -#include - -#define MP_INNER_MARGIN scale_(1.0) -#define MP_OUTER_MARGIN scale_(2.0) - -namespace Slic3r { - -class MotionPlanner; - -class MotionPlannerEnv -{ - friend class MotionPlanner; - -public: - MotionPlannerEnv() {}; - MotionPlannerEnv(const ExPolygon &island) : m_island(island), m_island_bbox(get_extents(island)) {}; - Point nearest_env_point(const Point &from, const Point &to) const; - bool island_contains(const Point &pt) const - { return m_island_bbox.contains(pt) && m_island.contains(pt); } - bool island_contains_b(const Point &pt) const - { return m_island_bbox.contains(pt) && m_island.contains_b(pt); } - -private: - ExPolygon m_island; - BoundingBox m_island_bbox; - // Region, where the travel is allowed. - ExPolygonCollection m_env; -}; - -// A 2D directed graph for searching a shortest path using the famous Dijkstra algorithm. -class MotionPlannerGraph -{ -public: - // Add a directed edge into the graph. - size_t add_node(const Point &p) { m_nodes.emplace_back(p); return m_nodes.size() - 1; } - void add_edge(size_t from, size_t to, double weight); - size_t find_closest_node(const Point &point) const { return point.nearest_point_index(m_nodes); } - - bool empty() const { return m_adjacency_list.empty(); } - Polyline shortest_path(size_t from, size_t to) const; - Polyline shortest_path(const Point &from, const Point &to) const - { return this->shortest_path(this->find_closest_node(from), this->find_closest_node(to)); } - -private: - typedef int node_t; - typedef double weight_t; - struct Neighbor { - Neighbor(node_t target, weight_t weight) : target(target), weight(weight) {} - node_t target; - weight_t weight; - }; - Points m_nodes; - std::vector> m_adjacency_list; -}; - -class MotionPlanner -{ -public: - MotionPlanner(const ExPolygons &islands); - ~MotionPlanner() {} - - Polyline shortest_path(const Point &from, const Point &to); - size_t islands_count() const { return m_islands.size(); } - -private: - bool m_initialized; - std::vector m_islands; - MotionPlannerEnv m_outer; - // 0th graph is the graph for m_outer. Other graphs are 1 indexed. - std::vector> m_graphs; - - void initialize(); - const MotionPlannerGraph& init_graph(int island_idx); - const MotionPlannerEnv& get_env(int island_idx) const - { return (island_idx == -1) ? m_outer : m_islands[island_idx]; } -}; - -} - -#endif diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 47961c623..9e406828a 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -15,8 +15,6 @@ REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); -//REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData"); -// REGISTER_CLASS(GCodeSender, "GCode::Sender"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); REGISTER_CLASS(LayerRegion, "Layer::Region"); @@ -35,7 +33,6 @@ REGISTER_CLASS(ModelMaterial, "Model::Material"); REGISTER_CLASS(ModelObject, "Model::Object"); REGISTER_CLASS(ModelVolume, "Model::Volume"); REGISTER_CLASS(ModelInstance, "Model::Instance"); -REGISTER_CLASS(MotionPlanner, "MotionPlanner"); REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox"); REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf"); REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3"); diff --git a/xs/t/18_motionplanner.t b/xs/t/18_motionplanner.t deleted file mode 100644 index dfcfec67f..000000000 --- a/xs/t/18_motionplanner.t +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../../lib"; -} - -use Slic3r::XS; -use Test::More tests => 20; - -my $square = Slic3r::Polygon->new( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -); -my $hole_in_square = Slic3r::Polygon->new( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], -); -$_->scale(1/0.000001) for $square, $hole_in_square; - -my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - -{ - my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(120, 120); - my $to = Slic3r::Point->new(180,180); - $_->scale(1/0.000001) for $from, $to; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; - ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; - ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; - ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; - ok $expolygon->contains_polyline($path), 'path is fully contained in expolygon'; -} - -{ - my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(80, 100); - my $to = Slic3r::Point->new(220,200); - $_->scale(1/0.000001) for $from, $to; - - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; - ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; - ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; - ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; - is scalar(@{ Slic3r::Geometry::Clipper::intersection_pl([$path], [@$expolygon]) }), 0, 'path has no intersection with expolygon'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->translate(300/0.000001, 0); - my $mp = Slic3r::MotionPlanner->new([ $expolygon, $expolygon2 ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(120, 120); - my $to = Slic3r::Point->new(120 + 300, 120); - $_->scale(1/0.000001) for $from, $to; - ok $expolygon->contains_point($from), 'start point is contained in first expolygon'; - ok $expolygon2->contains_point($to), 'end point is contained in second expolygon'; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; -} - -{ - my $expolygons = [ - Slic3r::ExPolygon->new([[123800962,89330311],[123959159,89699438],[124000004,89898430],[124000012,110116427],[123946510,110343065],[123767391,110701303],[123284087,111000001],[102585791,111000009],[102000004,110414223],[102000004,89585787],[102585790,89000000],[123300022,88999993]]), - Slic3r::ExPolygon->new([[97800954,89330311],[97959151,89699438],[97999996,89898430],[98000004,110116427],[97946502,110343065],[97767383,110701303],[97284079,111000001],[76585783,111000009],[75999996,110414223],[75999996,89585787],[76585782,89000000],[97300014,88999993]]), - ]; - my $mp = Slic3r::MotionPlanner->new($expolygons); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(79120520, 107839491); - my $to = Slic3r::Point->new(104664164, 108335852); - ok $expolygons->[1]->contains_point($from), 'start point is contained in second expolygon'; - ok $expolygons->[0]->contains_point($to), 'end point is contained in first expolygon'; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; -} - -__END__ diff --git a/xs/xsp/MotionPlanner.xsp b/xs/xsp/MotionPlanner.xsp deleted file mode 100644 index d93ed09ea..000000000 --- a/xs/xsp/MotionPlanner.xsp +++ /dev/null @@ -1,15 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/MotionPlanner.hpp" -%} - -%name{Slic3r::MotionPlanner} class MotionPlanner { - MotionPlanner(ExPolygons islands); - ~MotionPlanner(); - - int islands_count(); - Clone shortest_path(Point* from, Point* to) - %code%{ RETVAL = THIS->shortest_path(*from, *to); %}; -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 7e51b237c..2ecff6e3f 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -191,18 +191,6 @@ GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -//GCodePreviewData* O_OBJECT_SLIC3R -//Ref O_OBJECT_SLIC3R_T -//Clone O_OBJECT_SLIC3R_T - -MotionPlanner* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -// GCodeSender* O_OBJECT_SLIC3R -// Ref O_OBJECT_SLIC3R_T -// Clone O_OBJECT_SLIC3R_T - BridgeDetector* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 385b50f1a..2d364628e 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -97,12 +97,6 @@ %typemap{PolylineCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{MotionPlanner*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -// %typemap{GCodeSender*}; -// %typemap{Ref}{simple}; -// %typemap{Clone}{simple}; %typemap{BridgeDetector*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; From 62ab17bf6e337b7bd86424383d6eb6ff684b900f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 10:42:27 +0100 Subject: [PATCH 026/225] AvoidCrossingPerimeters: Refactored for better encapsulation. --- src/libslic3r/GCode.cpp | 48 +- .../GCode/AvoidCrossingPerimeters.cpp | 459 ++++++++++-------- .../GCode/AvoidCrossingPerimeters.hpp | 141 +----- 3 files changed, 303 insertions(+), 345 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 96eff24e7..47da80b6c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -46,8 +46,6 @@ using namespace std::literals::string_view_literals; #endif #include -#include -#include namespace Slic3r { @@ -232,7 +230,7 @@ namespace Slic3r { // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. gcode += gcodegen.retract(true); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); gcode += gcodegen.travel_to( wipe_tower_point_to_object_point(gcodegen, start_pos), erMixed, @@ -327,7 +325,7 @@ namespace Slic3r { } // Let the planner know we are traveling between objects. - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); return gcode; } @@ -1175,12 +1173,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Move to the origin position for the copy we're going to print. // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer - m_avoid_crossing_perimeters.use_external_mp_once = true; + m_avoid_crossing_perimeters.use_external_mp_once(); _write(file, this->retract()); _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. - m_avoid_crossing_perimeters.disable_once = true; + m_avoid_crossing_perimeters.disable_once(); // Ff we are printing the bottom layer of an object, and we have already finished // another one, set first layer temperatures. This happens before the Z move // is triggered, so machine has more time to reach such temperatures. @@ -1998,7 +1996,7 @@ void GCode::process_layer( if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { const std::pair loops = loops_it->second; this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp = true; + m_avoid_crossing_perimeters.use_external_mp(); Flow layer_skirt_flow(print.skirt_flow()); layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])); double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); @@ -2012,23 +2010,23 @@ void GCode::process_layer( //FIXME using the support_material_speed of the 1st object printed. gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value); } - m_avoid_crossing_perimeters.use_external_mp = false; + m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). if (first_layer && loops.first == 0) - m_avoid_crossing_perimeters.disable_once = true; + m_avoid_crossing_perimeters.disable_once(); } // Extrude brim with the extruder of the 1st region. if (! m_brim_done) { this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp = true; + m_avoid_crossing_perimeters.use_external_mp(); for (const ExtrusionEntity *ee : print.brim().entities) { gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value); } m_brim_done = true; - m_avoid_crossing_perimeters.use_external_mp = false; + m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. - m_avoid_crossing_perimeters.disable_once = true; + m_avoid_crossing_perimeters.disable_once(); } @@ -2055,7 +2053,7 @@ void GCode::process_layer( const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; std::pair this_object_copy(&instance_to_print.print_object, offset); if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once = true; + m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { @@ -2647,9 +2645,7 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by this->origin in order to get G-code coordinates. */ - Polyline travel; - travel.append(this->last_pos()); - travel.append(point); + Polyline travel { this->last_pos(), point }; // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); @@ -2660,31 +2656,28 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // multi-hop travel path inside the configuration space if (needs_retraction && m_config.avoid_crossing_perimeters - && ! m_avoid_crossing_perimeters.disable_once) { + && ! m_avoid_crossing_perimeters.disabled_once()) { travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); - // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role); //if (needs_retraction && m_layer_index > 1) exit(0); } // Re-allow avoid_crossing_perimeters for the next travel moves - m_avoid_crossing_perimeters.disable_once = false; - m_avoid_crossing_perimeters.use_external_mp_once = false; + m_avoid_crossing_perimeters.reset_once_modifiers(); // generate G-code for the travel move std::string gcode; if (needs_retraction) { - if (m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once && could_be_wipe_disabled) + if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled) m_wipe.reset_path(); Point last_post_before_retract = this->last_pos(); gcode += this->retract(); // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. // Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction() - if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once) { + if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) { Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract); - retract_travel.points.reserve(retract_travel.points.size() + travel.points.size()); append(retract_travel.points, travel.points); travel = std::move(retract_travel); } @@ -2693,11 +2686,10 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string m_wipe.reset_path(); // use G1 because we rely on paths being straight (G0 may make round paths) - Lines lines = travel.lines(); - if (! lines.empty()) { - for (const Line &line : lines) - gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment); - this->set_last_pos(lines.back().b); + if (travel.size() >= 2) { + for (size_t i = 1; i < travel.size(); ++ i) + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + this->set_last_pos(travel.points.back()); } return gcode; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 6a211e05b..7c0213556 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1,8 +1,6 @@ #include "../Layer.hpp" #include "../GCode.hpp" #include "../EdgeGrid.hpp" -#include "../Geometry.hpp" -#include "../ShortestPath.hpp" #include "../Print.hpp" #include "../Polygon.hpp" #include "../ExPolygon.hpp" @@ -10,15 +8,80 @@ #include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" -#include #include #include -#include -#include - namespace Slic3r { +struct TravelPoint +{ + Point point; + // Index of the polygon containing this point. A negative value indicates that the point is not on any border + int border_idx; +}; + +struct Intersection +{ + // Index of the polygon containing this point of intersection. + size_t border_idx; + // Index of the line on the polygon containing this point of intersection. + size_t line_idx; + // Point of intersection projected on the travel path. + Point point_transformed; + // Point of intersection. + Point point; + + Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) + : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; + + inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } +}; + +struct AllIntersectionsVisitor +{ + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) + : grid(grid), intersections(intersections) + {} + + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, + std::vector &intersections, + const Matrix2d &transform_to_x_axis, + const Line &travel_line) + : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + {} + + void reset() { + intersection_set.clear(); + } + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + + Point intersection_point; + if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, + (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + std::vector &intersections; + Matrix2d transform_to_x_axis; + Line travel_line; + std::unordered_set, boost::hash>> intersection_set; +}; + // Create a rotation matrix for projection on the given vector static Matrix2d rotation_by_direction(const Point &direction) { @@ -185,16 +248,16 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo return std::make_pair(std::move(contours), std::move(holes)); } -static Polyline to_polyline(const std::vector &travel) +static Polyline to_polyline(const std::vector &travel) { Polyline result; result.points.reserve(travel.size()); - for (const AvoidCrossingPerimeters::TravelPoint &t_point : travel) + for (const TravelPoint &t_point : travel) result.append(t_point.point); return result; } -static double travel_length(const std::vector &travel) { +static double travel_length(const std::vector &travel) { double total_length = 0; for (size_t idx = 1; idx < travel.size(); ++idx) total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); @@ -203,11 +266,11 @@ static double travel_length(const std::vector &intersections, - const std::string &path) +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const Polyline &result_travel, + const std::vector &intersections, + const std::string &path) { BoundingBox bbox = get_extents(boundary); ::Slic3r::SVG svg(path, bbox); @@ -217,21 +280,21 @@ static void export_travel_to_svg(const Polygons svg.draw(original_travel.a, "black"); svg.draw(original_travel.b, "grey"); - for (const AvoidCrossingPerimeters::Intersection &intersection : intersections) + for (const Intersection &intersection : intersections) svg.draw(intersection.point, "lightseagreen"); } -static void export_travel_to_svg(const Polygons &boundary, - const Line &original_travel, - const std::vector &result_travel, - const std::vector &intersections, - const std::string &path) +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const std::vector &result_travel, + const std::vector &intersections, + const std::string &path) { export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ -ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer) +static ExPolygons get_boundary(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -291,7 +354,7 @@ ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer) return result_boundary; } -ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer) +static ExPolygons get_boundary_external(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing_external(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -328,11 +391,12 @@ ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer) } // Returns a direction of the shortest path along the polygon boundary -AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) +enum class Direction { Forward, Backward }; +static Direction get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) { double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); @@ -358,165 +422,14 @@ AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_directi return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -std::vector AvoidCrossingPerimeters::simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) -{ - struct Visitor - { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} +static std::vector simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector& travel, const Polygons& boundaries, const bool use_heuristics); - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - - std::vector simplified_path; - simplified_path.reserve(travel.size()); - simplified_path.emplace_back(travel.front()); - - // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - const Point ¤t_point = travel[point_idx - 1].point; - TravelPoint next = travel[point_idx]; - - visitor.pt_current = ¤t_point; - - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel[point_idx_2].point == current_point) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - continue; - } - - visitor.pt_next = &travel[point_idx_2].point; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - } - } - - simplified_path.emplace_back(next); - } - - if(use_heuristics) { - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - } - - return simplified_path; -} - -std::vector AvoidCrossingPerimeters::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries) -{ - std::vector simplified_path; - std::vector intersections; - AllIntersectionsVisitor visitor(edge_grid, intersections); - simplified_path.reserve(travel.size()); - simplified_path.emplace_back(travel.front()); - for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - // Skip all indexes on the same polygon - while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) { - simplified_path.emplace_back(travel[point_idx]); - point_idx++; - } - - if (point_idx < travel.size()) { - const TravelPoint ¤t = travel[point_idx - 1]; - const TravelPoint &next = travel[point_idx]; - TravelPoint new_next = next; - size_t new_point_idx = point_idx; - double path_length = (next.point - current.point).cast().norm(); - double new_path_shorter_by = 0.; - size_t border_idx_change_count = 0; - std::vector shortcut; - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - const TravelPoint &possible_new_next = travel[point_idx_2]; - if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx) - border_idx_change_count++; - - if (border_idx_change_count >= 2) - break; - - path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast().norm(); - double shortcut_length = (possible_new_next.point - current.point).cast().norm(); - if ((path_length - shortcut_length) <= scale_(10.0)) - continue; - - intersections.clear(); - visitor.reset(); - visitor.travel_line.a = current.point; - visitor.travel_line.b = possible_new_next.point; - visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); - edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); - if (!intersections.empty()) { - std::sort(intersections.begin(), intersections.end()); - size_t last_border_idx_count = 0; - for (const Intersection &intersection : intersections) - if (int(intersection.border_idx) == possible_new_next.border_idx) - ++last_border_idx_count; - - if (last_border_idx_count > 0) - continue; - - std::vector possible_shortcut; - avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); - double shortcut_travel = travel_length(possible_shortcut); - if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { - new_path_shorter_by = path_length - shortcut_travel; - shortcut = possible_shortcut; - new_next = possible_new_next; - new_point_idx = point_idx_2; - } - } - } - - if (!shortcut.empty()) { - assert(shortcut.size() >= 2); - simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1); - point_idx = new_point_idx; - } - - simplified_path.emplace_back(new_next); - } - } - - return simplified_path; -} - -size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +static size_t avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -608,10 +521,164 @@ size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &bound return intersections.size(); } -bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen, - const Line & original_travel, - const Polyline &result_travel, - const size_t intersection_count) +static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries) +{ + std::vector simplified_path; + std::vector intersections; + AllIntersectionsVisitor visitor(edge_grid, intersections); + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + // Skip all indexes on the same polygon + while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) { + simplified_path.emplace_back(travel[point_idx]); + point_idx++; + } + + if (point_idx < travel.size()) { + const TravelPoint ¤t = travel[point_idx - 1]; + const TravelPoint &next = travel[point_idx]; + TravelPoint new_next = next; + size_t new_point_idx = point_idx; + double path_length = (next.point - current.point).cast().norm(); + double new_path_shorter_by = 0.; + size_t border_idx_change_count = 0; + std::vector shortcut; + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + const TravelPoint &possible_new_next = travel[point_idx_2]; + if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx) + border_idx_change_count++; + + if (border_idx_change_count >= 2) + break; + + path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast().norm(); + double shortcut_length = (possible_new_next.point - current.point).cast().norm(); + if ((path_length - shortcut_length) <= scale_(10.0)) + continue; + + intersections.clear(); + visitor.reset(); + visitor.travel_line.a = current.point; + visitor.travel_line.b = possible_new_next.point; + visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); + edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); + if (!intersections.empty()) { + std::sort(intersections.begin(), intersections.end()); + size_t last_border_idx_count = 0; + for (const Intersection &intersection : intersections) + if (int(intersection.border_idx) == possible_new_next.border_idx) + ++last_border_idx_count; + + if (last_border_idx_count > 0) + continue; + + std::vector possible_shortcut; + avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); + double shortcut_travel = travel_length(possible_shortcut); + if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { + new_path_shorter_by = path_length - shortcut_travel; + shortcut = possible_shortcut; + new_next = possible_new_next; + new_point_idx = point_idx_2; + } + } + } + + if (!shortcut.empty()) { + assert(shortcut.size() >= 2); + simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1); + point_idx = new_point_idx; + } + + simplified_path.emplace_back(new_next); + } + } + + return simplified_path; +} + +static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics) +{ + struct Visitor + { + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(edge_grid); + + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + + // Try to skip some points in the path. + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + continue; + } + + visitor.pt_next = &travel[point_idx_2].point; + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + } + } + + simplified_path.emplace_back(next); + } + + if(use_heuristics) { + simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); + std::reverse(simplified_path.begin(),simplified_path.end()); + simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); + std::reverse(simplified_path.begin(),simplified_path.end()); + } + + return simplified_path; +} + +static bool need_wipe(const GCode &gcodegen, + const ExPolygons &slice, + const Line &original_travel, + const Polyline &result_travel, + const size_t intersection_count) { bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; bool wipe_needed = false; @@ -622,19 +689,19 @@ bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen, // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. if (z_lift_enabled) { - if (any_expolygon_contains(m_slice, original_travel)) { + if (any_expolygon_contains(slice, original_travel)) { // Check if original_travel and result_travel are not same. // If both are the same, then it is possible to skip testing of result_travel if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { wipe_needed = false; } else { - wipe_needed = !any_expolygon_contains(m_slice, result_travel); + wipe_needed = !any_expolygon_contains(slice, result_travel); } } else { wipe_needed = true; } } else { - wipe_needed = !any_expolygon_contains(m_slice, result_travel); + wipe_needed = !any_expolygon_contains(slice, result_travel); } } @@ -646,7 +713,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; + bool use_external = m_use_external_mp || m_use_external_mp_once; Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); Point start = gcodegen.last_pos() + scaled_origin; Point end = point + scaled_origin; @@ -659,9 +726,9 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & std::vector result; auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); if (use_external) - travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); + travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); else - travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); + travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); result_pl = to_polyline(result); } @@ -678,7 +745,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !need_wipe(gcodegen, travel, result_pl, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, m_slice, travel, result_pl, travel_intersection_count); return result_pl; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 0f99108f9..d33311a90 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -5,120 +5,40 @@ #include "../ExPolygon.hpp" #include "../EdgeGrid.hpp" -#include -#include - namespace Slic3r { // Forward declarations. class GCode; class Layer; class Point; -class Print; -class PrintObject; - -struct PrintInstance; -using PrintObjectPtrs = std::vector; class AvoidCrossingPerimeters { public: - struct Intersection + // Routing around the objects vs. inside a single object. + void use_external_mp(bool use = true) { m_use_external_mp = use; }; + void use_external_mp_once() { m_use_external_mp_once = true; } + void disable_once() { m_disabled_once = true; } + bool disabled_once() const { return m_disabled_once; } + void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; } + + void init_layer(const Layer &layer); + + Polyline travel_to(const GCode& gcodegen, const Point& point) { - // Index of the polygon containing this point of intersection. - size_t border_idx; - // Index of the line on the polygon containing this point of intersection. - size_t line_idx; - // Point of intersection projected on the travel path. - Point point_transformed; - // Point of intersection. - Point point; + bool could_be_wipe_disabled; + return this->travel_to(gcodegen, point, &could_be_wipe_disabled); + } - Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) - : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - - inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } - }; - - struct TravelPoint - { - Point point; - // Index of the polygon containing this point. A negative value indicates that the point is not on any border - int border_idx; - }; - - struct AllIntersectionsVisitor - { - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) - : grid(grid), intersections(intersections) - {} - - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, - std::vector &intersections, - const Matrix2d &transform_to_x_axis, - const Line &travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) - {} - - void reset() { - intersection_set.clear(); - } - - bool operator()(coord_t iy, coord_t ix) - { - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - - Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && - intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); - intersection_set.insert(*it_contour_and_segment); - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &intersections; - Matrix2d transform_to_x_axis; - Line travel_line; - std::unordered_set, boost::hash>> intersection_set; - }; - - enum class Direction { Forward, Backward }; + Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); private: - static ExPolygons get_boundary(const Layer &layer); - - static ExPolygons get_boundary_external(const Layer &layer); - - static Direction get_shortest_direction( - const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); - - static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics); - - static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries); - - static size_t avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out); - - bool need_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); + bool m_use_external_mp { false }; + // just for the next travel move + bool m_use_external_mp_once { false }; + // this flag disables avoid_crossing_perimeters just for the next travel move + // we enable it by default for the first travel move in print + bool m_disabled_once { true }; // Slice of layer with elephant foot compensation ExPolygons m_slice; @@ -132,27 +52,6 @@ private: BoundingBox m_bbox_external; EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid_external; - -public: - // this flag triggers the use of the external configuration space - bool use_external_mp { false }; - // just for the next travel move - bool use_external_mp_once { false }; - // this flag disables avoid_crossing_perimeters just for the next travel move - // we enable it by default for the first travel move in print - bool disable_once { true }; - - AvoidCrossingPerimeters() = default; - - Polyline travel_to(const GCode& gcodegen, const Point& point) - { - bool could_be_wipe_disabled; - return this->travel_to(gcodegen, point, &could_be_wipe_disabled); - } - - Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); - - void init_layer(const Layer &layer); }; } // namespace Slic3r From 656b90dbe555406f07afee8d18655f30224f1d4b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 15:18:19 +0100 Subject: [PATCH 027/225] Avoid crossing perimeters: Further refactoring for clarity, code review. --- src/libslic3r/EdgeGrid.hpp | 9 +- src/libslic3r/ExPolygon.cpp | 6 +- src/libslic3r/ExPolygon.hpp | 3 +- .../GCode/AvoidCrossingPerimeters.cpp | 724 +++++++++--------- .../GCode/AvoidCrossingPerimeters.hpp | 4 +- src/libslic3r/MultiPoint.cpp | 7 - src/libslic3r/MultiPoint.hpp | 2 +- xs/CMakeLists.txt | 1 - 8 files changed, 363 insertions(+), 393 deletions(-) diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 6a9f482a1..bff56523b 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -238,7 +238,14 @@ public: { const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; size_t ipt = contour_and_segment_idx.second; - return std::pair(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]); + return std::pair(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); + } + + Line line(const std::pair &contour_and_segment_idx) const + { + const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; + size_t ipt = contour_and_segment_idx.second; + return Line(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); } protected: diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 5bdd5055e..f6e03dd1c 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -42,11 +42,11 @@ void ExPolygon::scale(double factor) hole.scale(factor); } -void ExPolygon::translate(double x, double y) +void ExPolygon::translate(const Point &p) { - contour.translate(x, y); + contour.translate(p); for (Polygon &hole : holes) - hole.translate(x, y); + hole.translate(p); } void ExPolygon::rotate(double angle) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 373853f97..0c8c42368 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -42,7 +42,8 @@ public: operator Polylines() const; void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } + void translate(const Point &vector); void rotate(double angle); void rotate(double angle, const Point ¢er); double area() const; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 7c0213556..f577c42a5 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -4,6 +4,7 @@ #include "../Print.hpp" #include "../Polygon.hpp" #include "../ExPolygon.hpp" +#include "../Geometry.hpp" #include "../ClipperUtils.hpp" #include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" @@ -16,7 +17,7 @@ namespace Slic3r { struct TravelPoint { Point point; - // Index of the polygon containing this point. A negative value indicates that the point is not on any border + // Index of the polygon containing this point. A negative value indicates that the point is not on any border. int border_idx; }; @@ -26,17 +27,11 @@ struct Intersection size_t border_idx; // Index of the line on the polygon containing this point of intersection. size_t line_idx; - // Point of intersection projected on the travel path. - Point point_transformed; // Point of intersection. Point point; - - Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) - : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - - inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } }; +// Finding all intersections of a set of contours with a line segment. struct AllIntersectionsVisitor { AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) @@ -45,9 +40,8 @@ struct AllIntersectionsVisitor AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections, - const Matrix2d &transform_to_x_axis, const Line &travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + : grid(grid), intersections(intersections), travel_line(travel_line) {} void reset() { @@ -58,16 +52,11 @@ struct AllIntersectionsVisitor { // Called with a row and colum of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + if (travel_line.intersection(grid.line(*it_contour_and_segment), &intersection_point) && intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersections.push_back({ it_contour_and_segment->first, it_contour_and_segment->second, intersection_point }); intersection_set.insert(*it_contour_and_segment); } } @@ -77,57 +66,42 @@ struct AllIntersectionsVisitor const EdgeGrid::Grid &grid; std::vector &intersections; - Matrix2d transform_to_x_axis; Line travel_line; std::unordered_set, boost::hash>> intersection_set; }; -// Create a rotation matrix for projection on the given vector -static Matrix2d rotation_by_direction(const Point &direction) -{ - Matrix2d rotation; - rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); - rotation(1, 0) = -rotation(0, 1); - rotation(1, 1) = rotation(0, 0); - - return rotation; -} - -static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) +template +static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) { assert(point_idx < polygon.size()); - if (point != polygon.points[point_idx]) - return polygon.points[point_idx]; - - int line_idx = int(point_idx); - if (forward) - for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); + auto line_idx = int(point_idx); + //FIXME endless loop if all points are equal to point? + if constexpr (forward) + for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); else - for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); + for (; point == polygon.points[line_idx]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); return polygon.points[line_idx]; } +//FIXME will be in Point.h in the master +template +inline Eigen::Matrix perp(const Eigen::MatrixBase>& v) { return Eigen::Matrix(-v.y(), v.x()); } + static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); assert(middle != right); - - Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); - Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); - normal_1.normalize(); - normal_2.normalize(); - - return (normal_1 + normal_2).normalized(); + return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); } // Compute normal of the polygon's vertex in an inward direction static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { - const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); - const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); + const size_t left_idx = point_idx == 0 ? polygon.size() - 1 : point_idx - 1; + const size_t right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1; const Point &middle = polygon.points[point_idx]; - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); return three_points_inward_normal(left, middle, right); } @@ -140,114 +114,11 @@ static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t poin // Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset) { - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); } -static bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) -{ - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - // When both endpoints are out of the bounding box, it needs to check in more detail. - if (start_out_of_bound && end_out_of_bound) { - Point intersection; - return bbox.polygon().intersection(Line(start, end), &intersection); - } - return true; -} - -static std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) -{ - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - Point start_clamped = start, end_clamped = end; - Points intersections; - if (start_out_of_bound || end_out_of_bound) { - bbox.polygon().intersections(Line(start, end), &intersections); - assert(intersections.size() <= 2); - } - - if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { - start_clamped = intersections[0]; - } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { - end_clamped = intersections[0]; - } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { - if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { - start_clamped = intersections[0]; - end_clamped = intersections[1]; - } else { - start_clamped = intersections[1]; - end_clamped = intersections[0]; - } - } - - return std::make_pair(start_clamped, end_clamped); -} - -static inline float get_default_perimeter_spacing(const Print &print) -{ - const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; - return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); -} - -static float get_perimeter_spacing(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const LayerRegion *layer_region : layer.regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - -static float get_perimeter_spacing_external(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const PrintObject *object : layer.object()->print()->objects()) - for (Layer *l : object->layers()) - if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) - for (const LayerRegion *layer_region : l->regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - -// Check if anyone of ExPolygons contains whole travel. -template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) -{ - for (const ExPolygon &ex_polygon : ex_polygons) - if (ex_polygon.contains(travel)) return true; - - return false; -} - -static std::pair split_expolygon(const ExPolygons &ex_polygons) -{ - Polygons contours, holes; - contours.reserve(ex_polygons.size()); - holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), - [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); - for (const ExPolygon &ex_poly : ex_polygons) { - contours.emplace_back(ex_poly.contour); - append(holes, ex_poly.holes); - } - return std::make_pair(std::move(contours), std::move(holes)); -} - static Polyline to_polyline(const std::vector &travel) { Polyline result; @@ -265,6 +136,8 @@ static double travel_length(const std::vector &travel) { return total_length; } +// #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT static void export_travel_to_svg(const Polygons &boundary, const Line &original_travel, @@ -294,102 +167,6 @@ static void export_travel_to_svg(const Polygons &boundary, } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ -static ExPolygons get_boundary(const Layer &layer) -{ - const float perimeter_spacing = get_perimeter_spacing(layer); - const float perimeter_offset = perimeter_spacing / 2.f; - size_t polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - polygons_count += layer_region->slices.surfaces.size(); - - ExPolygons boundary; - boundary.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); - - boundary = union_ex(boundary); - ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); - ExPolygons result_boundary; - if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, - offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), - perimeter_offset + float(SCALED_EPSILON)); - perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); - perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); - perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - // By calling intersection_ex some artifacts arose by previous operations are removed. - result_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary)); - } else { - result_boundary = std::move(perimeter_boundary); - } - - auto [contours, holes] = split_expolygon(boundary); - // Add an outer boundary to avoid crossing perimeters from supports - ExPolygons outer_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); - result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); - ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); - result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - result_boundary = union_ex(result_boundary); - - // Collect all top layers that will not be crossed. - polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) ++polygons_count; - - if (polygons_count > 0) { - ExPolygons top_layer_polygons; - top_layer_polygons.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); - - top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); - } - - return result_boundary; -} - -static ExPolygons get_boundary_external(const Layer &layer) -{ - const float perimeter_spacing = get_perimeter_spacing_external(layer); - const float perimeter_offset = perimeter_spacing / 2.f; - ExPolygons boundary; - // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". - for (const PrintObject *object : layer.object()->print()->objects()) { - ExPolygons polygons_per_obj; - for (Layer *l : object->layers()) - if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) - for (const LayerRegion *layer_region : l->regions()) - for (const Surface &surface : layer_region->slices.surfaces) - polygons_per_obj.emplace_back(surface.expolygon); - - for (const PrintInstance &instance : object->instances()) { - size_t boundary_idx = boundary.size(); - boundary.reserve(boundary.size() + polygons_per_obj.size()); - boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); - for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); - } - } - boundary = union_ex(boundary); - auto [contours, holes] = split_expolygon(boundary); - // Polygons in which is possible traveling without crossing perimeters of another object. - // A convex hull allows removing unnecessary detour caused by following the boundary of the object. - ExPolygons result_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); - // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. - ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); - result_boundary.reserve(result_boundary.size() + holes_boundary.size()); - result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - result_boundary = union_ex(result_boundary); - return result_boundary; -} - // Returns a direction of the shortest path along the polygon boundary enum class Direction { Forward, Backward }; static Direction get_shortest_direction(const Lines &lines, @@ -422,29 +199,89 @@ static Direction get_shortest_direction(const Lines &lines, return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -static std::vector simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector& travel, const Polygons& boundaries, const bool use_heuristics); - -static size_t avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +// Straighten the travel path as long as it does not collide with the contours stored in edge_grid. +static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, const std::vector &travel) { - const Point direction = end - start; - Matrix2d transform_to_x_axis = rotation_by_direction(direction); - - const Line travel_line_orig(start, end); - const Line travel_line((transform_to_x_axis * start.cast()).cast(), - (transform_to_x_axis * end.cast()).cast()); - - std::vector intersections; + // Visitor to check for a collision of a line segment with any contour stored inside the edge_grid. + struct Visitor { - AllIntersectionsVisitor visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - edge_grid.visit_cells_intersecting_line(start, end, visitor); + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(edge_grid); + + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + + // Try to skip some points in the path. + //FIXME maybe use a binary search to trim the line? + //FIXME how about searching tangent point at long segments? + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + continue; + } + + visitor.pt_next = &travel[point_idx_2].point; + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + } + } + + simplified_path.emplace_back(next); } - std::sort(intersections.begin(), intersections.end()); + return simplified_path; +} + +// Called by avoid_perimeters() and by simplify_travel_heuristics(). +static size_t avoid_perimeters_inner(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + std::vector &result_out) +{ + // Find all intersections between boundaries and the line segment, sort them along the line segment. + std::vector intersections; + { + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); + edge_grid.visit_cells_intersecting_line(start, end, visitor); + Vec2d dir = (end - start).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).cast().dot(dir) > 0.; }); + } std::vector result; result.push_back({start, -1}); @@ -500,24 +337,23 @@ static size_t avoid_perimeters(const Polygons &boundaries, #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, travel_line_orig, result, intersections, - debug_out_path("AvoidCrossingPerimeters-initial-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - if(!intersections.empty()) - result = simplify_travel(edge_grid, result, boundaries, use_heuristics); + if (! intersections.empty()) + result = simplify_travel(edge_grid, result); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, travel_line_orig, result, intersections, - debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - result_out->reserve(result_out->size() + result.size()); - result_out->insert(result_out->end(), result.begin(), result.end()); + append(result_out, std::move(result)); return intersections.size(); } @@ -563,10 +399,10 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid visitor.reset(); visitor.travel_line.a = current.point; visitor.travel_line.b = possible_new_next.point; - visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); if (!intersections.empty()) { - std::sort(intersections.begin(), intersections.end()); + Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).cast().dot(dir) > 0.; }); size_t last_border_idx_count = 0; for (const Intersection &intersection : intersections) if (int(intersection.border_idx) == possible_new_next.border_idx) @@ -576,9 +412,9 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid continue; std::vector possible_shortcut; - avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); + avoid_perimeters_inner(boundaries, edge_grid, current.point, possible_new_next.point, possible_shortcut); double shortcut_travel = travel_length(possible_shortcut); - if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { + if (path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by) { new_path_shorter_by = path_length - shortcut_travel; shortcut = possible_shortcut; new_next = possible_new_next; @@ -600,78 +436,44 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid return simplified_path; } -static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) +// Called by AvoidCrossingPerimeters::travel_to() +static size_t avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + Polyline &result_out) { - struct Visitor + // Travel line is completely or partially inside the bounding box. + std::vector path; + size_t num_intersections = avoid_perimeters_inner(boundaries, edge_grid, start, end, path); + if (num_intersections) { + path = simplify_travel_heuristics(edge_grid, path, boundaries); + std::reverse(path.begin(), path.end()); + path = simplify_travel_heuristics(edge_grid, path, boundaries); + std::reverse(path.begin(), path.end()); + } + + result_out = to_polyline(path); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. + return num_intersections; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() +template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) +{ + //FIXME filter by bounding boxes! + for (const ExPolygon &ex_polygon : ex_polygons) + if (ex_polygon.contains(travel)) return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - - std::vector simplified_path; - simplified_path.reserve(travel.size()); - simplified_path.emplace_back(travel.front()); - - // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - const Point ¤t_point = travel[point_idx - 1].point; - TravelPoint next = travel[point_idx]; - - visitor.pt_current = ¤t_point; - - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel[point_idx_2].point == current_point) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - continue; - } - - visitor.pt_next = &travel[point_idx_2].point; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - } - } - - simplified_path.emplace_back(next); - } - - if(use_heuristics) { - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - } - - return simplified_path; + return false; } static bool need_wipe(const GCode &gcodegen, @@ -692,11 +494,8 @@ static bool need_wipe(const GCode &gcodegen, if (any_expolygon_contains(slice, original_travel)) { // Check if original_travel and result_travel are not same. // If both are the same, then it is possible to skip testing of result_travel - if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { - wipe_needed = false; - } else { - wipe_needed = !any_expolygon_contains(slice, result_travel); - } + wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && + !any_expolygon_contains(slice, result_travel); } else { wipe_needed = true; } @@ -719,28 +518,27 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & Point end = point + scaled_origin; Polyline result_pl; size_t travel_intersection_count = 0; - if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result_pl = Polyline({start, end}); - travel_intersection_count = 0; + Vec2d startf = start.cast(); + Vec2d endf = end .cast(); + // Trim the travel line by the bounding box. + if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { + // Travel line is completely or partially inside the bounding box. + travel_intersection_count = use_external ? + avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast(), endf.cast(), result_pl) : + avoid_perimeters(m_boundaries, m_grid, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; } else { - std::vector result; - auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); - if (use_external) - travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); - else - travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); - - result_pl = to_polyline(result); + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; } - result_pl.points.front() = start; - result_pl.points.back() = end; - Line travel(start, end); double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); - if ((max_detour_length > 0) && ((result_pl.length() - travel.length()) > max_detour_length)) { - result_pl = Polyline({start, end}); - } + if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) + result_pl = {start, end}; + if (use_external) { result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; @@ -750,6 +548,172 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & return result_pl; } +// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** + +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const Print &print) +{ + //FIXME better use extruders printing this PrintObject or this Print? + //FIXME maybe better use an average of printing extruders? + const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; + return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); +} + +// called by get_boundary() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + //FIXME not all regions are printing. Collect only non-empty regions? + for (const LayerRegion *layer_region : layer.regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + //FIXME not all regions are printing. Collect only non-empty regions? + for (const LayerRegion *layer_region : l->regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +// called by AvoidCrossingPerimeters::init_layer()->get_boundary()/get_boundary_external() +static std::pair split_expolygon(const ExPolygons &ex_polygons) +{ + Polygons contours, holes; + contours.reserve(ex_polygons.size()); + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), + [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); + for (const ExPolygon &ex_poly : ex_polygons) { + contours.emplace_back(ex_poly.contour); + append(holes, ex_poly.holes); + } + return std::make_pair(std::move(contours), std::move(holes)); +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) + boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); + ExPolygons result_boundary; + if (perimeter_boundary.size() != boundary.size()) { + //FIXME ??? + // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, + offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), + perimeter_offset + float(SCALED_EPSILON)); + perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); + append(perimeter_boundary, std::move(missing_perimeter_boundary)); + // By calling intersection_ex some artifacts arose by previous operations are removed. + result_boundary = intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary); + } else { + result_boundary = std::move(perimeter_boundary); + } + + auto [contours, holes] = split_expolygon(boundary); + // Add an outer boundary to avoid crossing perimeters from supports + ExPolygons outer_boundary = union_ex( + //FIXME flip order of offset and convex_hull + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); + ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + } + + return result_boundary; +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary_external(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing_external(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + ExPolygons boundary; + // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". + for (const PrintObject *object : layer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift); + } + } + boundary = union_ex(boundary); + auto [contours, holes] = split_expolygon(boundary); + // Polygons in which is possible traveling without crossing perimeters of another object. + // A convex hull allows removing unnecessary detour caused by following the boundary of the object. + ExPolygons result_boundary = union_ex( + //FIXME flip order of offset and convex_hull + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. + append(result_boundary, union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)))); + return union_ex(result_boundary); +} + void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_slice.clear(); @@ -757,19 +721,25 @@ void AvoidCrossingPerimeters::init_layer(const Layer &layer) m_boundaries_external.clear(); for (const LayerRegion *layer_region : layer.regions()) + //FIXME making copies? append(m_slice, (ExPolygons) layer_region->slices); m_boundaries = to_polygons(get_boundary(layer)); m_boundaries_external = to_polygons(get_boundary_external(layer)); - m_bbox = get_extents(m_boundaries); - m_bbox.offset(SCALED_EPSILON); - m_bbox_external = get_extents(m_boundaries_external); - m_bbox_external.offset(SCALED_EPSILON); + BoundingBox bbox(get_extents(m_boundaries)); + bbox.offset(SCALED_EPSILON); + BoundingBox bbox_external = get_extents(m_boundaries_external); + bbox_external.offset(SCALED_EPSILON); - m_grid.set_bbox(m_bbox); + m_bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_bbox_external = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); + + m_grid.set_bbox(bbox); + //FIXME 1mm grid? m_grid.create(m_boundaries, coord_t(scale_(1.))); - m_grid_external.set_bbox(m_bbox_external); + m_grid_external.set_bbox(bbox_external); + //FIXME 1mm grid? m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index d33311a90..bdae775a1 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -47,9 +47,9 @@ private: // Collection of boundaries used for detection of crossing perimetrs for travels outside object Polygons m_boundaries_external; // Bounding box of m_boundaries - BoundingBox m_bbox; + BoundingBoxf m_bbox; // Bounding box of m_boundaries_external - BoundingBox m_bbox_external; + BoundingBoxf m_bbox_external; EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid_external; }; diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index a00dd802b..03f7ff59c 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -18,13 +18,6 @@ void MultiPoint::scale(double factor_x, double factor_y) } } -void MultiPoint::translate(double x, double y) -{ - Vector v(x, y); - for (Point &pt : points) - pt += v; -} - void MultiPoint::translate(const Point &v) { for (Point &pt : points) diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 653e59cd8..b7a5ab684 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -28,7 +28,7 @@ public: MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } void scale(double factor); void scale(double factor_x, double factor_y); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } void translate(const Point &vector); void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 75d236a54..962e2e04d 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -62,7 +62,6 @@ set(XS_XSP_FILES ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp - ${XSP_DIR}/MotionPlanner.xsp ${XSP_DIR}/PerimeterGenerator.xsp ${XSP_DIR}/PlaceholderParser.xsp ${XSP_DIR}/Point.xsp From f206b743fd8195a41e9aa13d17f865a35fc97693 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 15:18:19 +0100 Subject: [PATCH 028/225] Avoid crossing perimeters: Further refactoring for clarity, code review. --- src/libslic3r/EdgeGrid.hpp | 9 +- src/libslic3r/ExPolygon.cpp | 6 +- src/libslic3r/ExPolygon.hpp | 3 +- .../GCode/AvoidCrossingPerimeters.cpp | 724 +++++++++--------- .../GCode/AvoidCrossingPerimeters.hpp | 4 +- src/libslic3r/MultiPoint.cpp | 7 - src/libslic3r/MultiPoint.hpp | 2 +- xs/CMakeLists.txt | 1 - 8 files changed, 363 insertions(+), 393 deletions(-) diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 6a9f482a1..bff56523b 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -238,7 +238,14 @@ public: { const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; size_t ipt = contour_and_segment_idx.second; - return std::pair(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]); + return std::pair(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); + } + + Line line(const std::pair &contour_and_segment_idx) const + { + const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; + size_t ipt = contour_and_segment_idx.second; + return Line(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); } protected: diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 5bdd5055e..f6e03dd1c 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -42,11 +42,11 @@ void ExPolygon::scale(double factor) hole.scale(factor); } -void ExPolygon::translate(double x, double y) +void ExPolygon::translate(const Point &p) { - contour.translate(x, y); + contour.translate(p); for (Polygon &hole : holes) - hole.translate(x, y); + hole.translate(p); } void ExPolygon::rotate(double angle) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 373853f97..0c8c42368 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -42,7 +42,8 @@ public: operator Polylines() const; void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } + void translate(const Point &vector); void rotate(double angle); void rotate(double angle, const Point ¢er); double area() const; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 7c0213556..f757a268e 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -4,6 +4,7 @@ #include "../Print.hpp" #include "../Polygon.hpp" #include "../ExPolygon.hpp" +#include "../Geometry.hpp" #include "../ClipperUtils.hpp" #include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" @@ -16,7 +17,7 @@ namespace Slic3r { struct TravelPoint { Point point; - // Index of the polygon containing this point. A negative value indicates that the point is not on any border + // Index of the polygon containing this point. A negative value indicates that the point is not on any border. int border_idx; }; @@ -26,17 +27,11 @@ struct Intersection size_t border_idx; // Index of the line on the polygon containing this point of intersection. size_t line_idx; - // Point of intersection projected on the travel path. - Point point_transformed; // Point of intersection. Point point; - - Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) - : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - - inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } }; +// Finding all intersections of a set of contours with a line segment. struct AllIntersectionsVisitor { AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) @@ -45,9 +40,8 @@ struct AllIntersectionsVisitor AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections, - const Matrix2d &transform_to_x_axis, const Line &travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + : grid(grid), intersections(intersections), travel_line(travel_line) {} void reset() { @@ -58,16 +52,11 @@ struct AllIntersectionsVisitor { // Called with a row and colum of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + if (travel_line.intersection(grid.line(*it_contour_and_segment), &intersection_point) && intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersections.push_back({ it_contour_and_segment->first, it_contour_and_segment->second, intersection_point }); intersection_set.insert(*it_contour_and_segment); } } @@ -77,57 +66,42 @@ struct AllIntersectionsVisitor const EdgeGrid::Grid &grid; std::vector &intersections; - Matrix2d transform_to_x_axis; Line travel_line; std::unordered_set, boost::hash>> intersection_set; }; -// Create a rotation matrix for projection on the given vector -static Matrix2d rotation_by_direction(const Point &direction) -{ - Matrix2d rotation; - rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); - rotation(1, 0) = -rotation(0, 1); - rotation(1, 1) = rotation(0, 0); - - return rotation; -} - -static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) +template +static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) { assert(point_idx < polygon.size()); - if (point != polygon.points[point_idx]) - return polygon.points[point_idx]; - - int line_idx = int(point_idx); - if (forward) - for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); + auto line_idx = int(point_idx); + //FIXME endless loop if all points are equal to point? + if constexpr (forward) + for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); else - for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); + for (; point == polygon.points[line_idx]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); return polygon.points[line_idx]; } +//FIXME will be in Point.h in the master +template +inline Eigen::Matrix perp(const Eigen::MatrixBase>& v) { return Eigen::Matrix(-v.y(), v.x()); } + static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); assert(middle != right); - - Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); - Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); - normal_1.normalize(); - normal_2.normalize(); - - return (normal_1 + normal_2).normalized(); + return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); } // Compute normal of the polygon's vertex in an inward direction static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { - const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); - const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); + const size_t left_idx = point_idx == 0 ? polygon.size() - 1 : point_idx - 1; + const size_t right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1; const Point &middle = polygon.points[point_idx]; - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); return three_points_inward_normal(left, middle, right); } @@ -140,114 +114,11 @@ static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t poin // Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset) { - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); } -static bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) -{ - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - // When both endpoints are out of the bounding box, it needs to check in more detail. - if (start_out_of_bound && end_out_of_bound) { - Point intersection; - return bbox.polygon().intersection(Line(start, end), &intersection); - } - return true; -} - -static std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) -{ - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - Point start_clamped = start, end_clamped = end; - Points intersections; - if (start_out_of_bound || end_out_of_bound) { - bbox.polygon().intersections(Line(start, end), &intersections); - assert(intersections.size() <= 2); - } - - if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { - start_clamped = intersections[0]; - } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { - end_clamped = intersections[0]; - } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { - if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { - start_clamped = intersections[0]; - end_clamped = intersections[1]; - } else { - start_clamped = intersections[1]; - end_clamped = intersections[0]; - } - } - - return std::make_pair(start_clamped, end_clamped); -} - -static inline float get_default_perimeter_spacing(const Print &print) -{ - const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; - return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); -} - -static float get_perimeter_spacing(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const LayerRegion *layer_region : layer.regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - -static float get_perimeter_spacing_external(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const PrintObject *object : layer.object()->print()->objects()) - for (Layer *l : object->layers()) - if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) - for (const LayerRegion *layer_region : l->regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - -// Check if anyone of ExPolygons contains whole travel. -template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) -{ - for (const ExPolygon &ex_polygon : ex_polygons) - if (ex_polygon.contains(travel)) return true; - - return false; -} - -static std::pair split_expolygon(const ExPolygons &ex_polygons) -{ - Polygons contours, holes; - contours.reserve(ex_polygons.size()); - holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), - [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); - for (const ExPolygon &ex_poly : ex_polygons) { - contours.emplace_back(ex_poly.contour); - append(holes, ex_poly.holes); - } - return std::make_pair(std::move(contours), std::move(holes)); -} - static Polyline to_polyline(const std::vector &travel) { Polyline result; @@ -265,6 +136,8 @@ static double travel_length(const std::vector &travel) { return total_length; } +// #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT static void export_travel_to_svg(const Polygons &boundary, const Line &original_travel, @@ -294,102 +167,6 @@ static void export_travel_to_svg(const Polygons &boundary, } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ -static ExPolygons get_boundary(const Layer &layer) -{ - const float perimeter_spacing = get_perimeter_spacing(layer); - const float perimeter_offset = perimeter_spacing / 2.f; - size_t polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - polygons_count += layer_region->slices.surfaces.size(); - - ExPolygons boundary; - boundary.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); - - boundary = union_ex(boundary); - ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); - ExPolygons result_boundary; - if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, - offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), - perimeter_offset + float(SCALED_EPSILON)); - perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); - perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); - perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - // By calling intersection_ex some artifacts arose by previous operations are removed. - result_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary)); - } else { - result_boundary = std::move(perimeter_boundary); - } - - auto [contours, holes] = split_expolygon(boundary); - // Add an outer boundary to avoid crossing perimeters from supports - ExPolygons outer_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); - result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); - ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); - result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - result_boundary = union_ex(result_boundary); - - // Collect all top layers that will not be crossed. - polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) ++polygons_count; - - if (polygons_count > 0) { - ExPolygons top_layer_polygons; - top_layer_polygons.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); - - top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); - } - - return result_boundary; -} - -static ExPolygons get_boundary_external(const Layer &layer) -{ - const float perimeter_spacing = get_perimeter_spacing_external(layer); - const float perimeter_offset = perimeter_spacing / 2.f; - ExPolygons boundary; - // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". - for (const PrintObject *object : layer.object()->print()->objects()) { - ExPolygons polygons_per_obj; - for (Layer *l : object->layers()) - if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) - for (const LayerRegion *layer_region : l->regions()) - for (const Surface &surface : layer_region->slices.surfaces) - polygons_per_obj.emplace_back(surface.expolygon); - - for (const PrintInstance &instance : object->instances()) { - size_t boundary_idx = boundary.size(); - boundary.reserve(boundary.size() + polygons_per_obj.size()); - boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); - for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); - } - } - boundary = union_ex(boundary); - auto [contours, holes] = split_expolygon(boundary); - // Polygons in which is possible traveling without crossing perimeters of another object. - // A convex hull allows removing unnecessary detour caused by following the boundary of the object. - ExPolygons result_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); - // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. - ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); - result_boundary.reserve(result_boundary.size() + holes_boundary.size()); - result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - result_boundary = union_ex(result_boundary); - return result_boundary; -} - // Returns a direction of the shortest path along the polygon boundary enum class Direction { Forward, Backward }; static Direction get_shortest_direction(const Lines &lines, @@ -422,29 +199,89 @@ static Direction get_shortest_direction(const Lines &lines, return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -static std::vector simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector& travel, const Polygons& boundaries, const bool use_heuristics); - -static size_t avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +// Straighten the travel path as long as it does not collide with the contours stored in edge_grid. +static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, const std::vector &travel) { - const Point direction = end - start; - Matrix2d transform_to_x_axis = rotation_by_direction(direction); - - const Line travel_line_orig(start, end); - const Line travel_line((transform_to_x_axis * start.cast()).cast(), - (transform_to_x_axis * end.cast()).cast()); - - std::vector intersections; + // Visitor to check for a collision of a line segment with any contour stored inside the edge_grid. + struct Visitor { - AllIntersectionsVisitor visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - edge_grid.visit_cells_intersecting_line(start, end, visitor); + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(edge_grid); + + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + + // Try to skip some points in the path. + //FIXME maybe use a binary search to trim the line? + //FIXME how about searching tangent point at long segments? + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + continue; + } + + visitor.pt_next = &travel[point_idx_2].point; + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + } + } + + simplified_path.emplace_back(next); } - std::sort(intersections.begin(), intersections.end()); + return simplified_path; +} + +// Called by avoid_perimeters() and by simplify_travel_heuristics(). +static size_t avoid_perimeters_inner(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + std::vector &result_out) +{ + // Find all intersections between boundaries and the line segment, sort them along the line segment. + std::vector intersections; + { + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); + edge_grid.visit_cells_intersecting_line(start, end, visitor); + Vec2d dir = (end - start).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + } std::vector result; result.push_back({start, -1}); @@ -500,24 +337,23 @@ static size_t avoid_perimeters(const Polygons &boundaries, #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, travel_line_orig, result, intersections, - debug_out_path("AvoidCrossingPerimeters-initial-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - if(!intersections.empty()) - result = simplify_travel(edge_grid, result, boundaries, use_heuristics); + if (! intersections.empty()) + result = simplify_travel(edge_grid, result); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, travel_line_orig, result, intersections, - debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - result_out->reserve(result_out->size() + result.size()); - result_out->insert(result_out->end(), result.begin(), result.end()); + append(result_out, std::move(result)); return intersections.size(); } @@ -563,10 +399,10 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid visitor.reset(); visitor.travel_line.a = current.point; visitor.travel_line.b = possible_new_next.point; - visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); if (!intersections.empty()) { - std::sort(intersections.begin(), intersections.end()); + Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); size_t last_border_idx_count = 0; for (const Intersection &intersection : intersections) if (int(intersection.border_idx) == possible_new_next.border_idx) @@ -576,9 +412,9 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid continue; std::vector possible_shortcut; - avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); + avoid_perimeters_inner(boundaries, edge_grid, current.point, possible_new_next.point, possible_shortcut); double shortcut_travel = travel_length(possible_shortcut); - if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { + if (path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by) { new_path_shorter_by = path_length - shortcut_travel; shortcut = possible_shortcut; new_next = possible_new_next; @@ -600,78 +436,44 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid return simplified_path; } -static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) +// Called by AvoidCrossingPerimeters::travel_to() +static size_t avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + Polyline &result_out) { - struct Visitor + // Travel line is completely or partially inside the bounding box. + std::vector path; + size_t num_intersections = avoid_perimeters_inner(boundaries, edge_grid, start, end, path); + if (num_intersections) { + path = simplify_travel_heuristics(edge_grid, path, boundaries); + std::reverse(path.begin(), path.end()); + path = simplify_travel_heuristics(edge_grid, path, boundaries); + std::reverse(path.begin(), path.end()); + } + + result_out = to_polyline(path); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. + return num_intersections; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() +template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) +{ + //FIXME filter by bounding boxes! + for (const ExPolygon &ex_polygon : ex_polygons) + if (ex_polygon.contains(travel)) return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - - std::vector simplified_path; - simplified_path.reserve(travel.size()); - simplified_path.emplace_back(travel.front()); - - // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - const Point ¤t_point = travel[point_idx - 1].point; - TravelPoint next = travel[point_idx]; - - visitor.pt_current = ¤t_point; - - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel[point_idx_2].point == current_point) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - continue; - } - - visitor.pt_next = &travel[point_idx_2].point; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - } - } - - simplified_path.emplace_back(next); - } - - if(use_heuristics) { - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - } - - return simplified_path; + return false; } static bool need_wipe(const GCode &gcodegen, @@ -692,11 +494,8 @@ static bool need_wipe(const GCode &gcodegen, if (any_expolygon_contains(slice, original_travel)) { // Check if original_travel and result_travel are not same. // If both are the same, then it is possible to skip testing of result_travel - if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { - wipe_needed = false; - } else { - wipe_needed = !any_expolygon_contains(slice, result_travel); - } + wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && + !any_expolygon_contains(slice, result_travel); } else { wipe_needed = true; } @@ -719,28 +518,27 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & Point end = point + scaled_origin; Polyline result_pl; size_t travel_intersection_count = 0; - if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result_pl = Polyline({start, end}); - travel_intersection_count = 0; + Vec2d startf = start.cast(); + Vec2d endf = end .cast(); + // Trim the travel line by the bounding box. + if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { + // Travel line is completely or partially inside the bounding box. + travel_intersection_count = use_external ? + avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast(), endf.cast(), result_pl) : + avoid_perimeters(m_boundaries, m_grid, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; } else { - std::vector result; - auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); - if (use_external) - travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); - else - travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); - - result_pl = to_polyline(result); + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; } - result_pl.points.front() = start; - result_pl.points.back() = end; - Line travel(start, end); double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); - if ((max_detour_length > 0) && ((result_pl.length() - travel.length()) > max_detour_length)) { - result_pl = Polyline({start, end}); - } + if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) + result_pl = {start, end}; + if (use_external) { result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; @@ -750,6 +548,172 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & return result_pl; } +// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** + +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const Print &print) +{ + //FIXME better use extruders printing this PrintObject or this Print? + //FIXME maybe better use an average of printing extruders? + const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; + return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); +} + +// called by get_boundary() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + //FIXME not all regions are printing. Collect only non-empty regions? + for (const LayerRegion *layer_region : layer.regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + //FIXME not all regions are printing. Collect only non-empty regions? + for (const LayerRegion *layer_region : l->regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +// called by AvoidCrossingPerimeters::init_layer()->get_boundary()/get_boundary_external() +static std::pair split_expolygon(const ExPolygons &ex_polygons) +{ + Polygons contours, holes; + contours.reserve(ex_polygons.size()); + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), + [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); + for (const ExPolygon &ex_poly : ex_polygons) { + contours.emplace_back(ex_poly.contour); + append(holes, ex_poly.holes); + } + return std::make_pair(std::move(contours), std::move(holes)); +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) + boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); + ExPolygons result_boundary; + if (perimeter_boundary.size() != boundary.size()) { + //FIXME ??? + // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, + offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), + perimeter_offset + float(SCALED_EPSILON)); + perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); + append(perimeter_boundary, std::move(missing_perimeter_boundary)); + // By calling intersection_ex some artifacts arose by previous operations are removed. + result_boundary = intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary); + } else { + result_boundary = std::move(perimeter_boundary); + } + + auto [contours, holes] = split_expolygon(boundary); + // Add an outer boundary to avoid crossing perimeters from supports + ExPolygons outer_boundary = union_ex( + //FIXME flip order of offset and convex_hull + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); + ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + } + + return result_boundary; +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary_external(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing_external(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + ExPolygons boundary; + // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". + for (const PrintObject *object : layer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift); + } + } + boundary = union_ex(boundary); + auto [contours, holes] = split_expolygon(boundary); + // Polygons in which is possible traveling without crossing perimeters of another object. + // A convex hull allows removing unnecessary detour caused by following the boundary of the object. + ExPolygons result_boundary = union_ex( + //FIXME flip order of offset and convex_hull + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. + append(result_boundary, union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)))); + return union_ex(result_boundary); +} + void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_slice.clear(); @@ -757,19 +721,25 @@ void AvoidCrossingPerimeters::init_layer(const Layer &layer) m_boundaries_external.clear(); for (const LayerRegion *layer_region : layer.regions()) + //FIXME making copies? append(m_slice, (ExPolygons) layer_region->slices); m_boundaries = to_polygons(get_boundary(layer)); m_boundaries_external = to_polygons(get_boundary_external(layer)); - m_bbox = get_extents(m_boundaries); - m_bbox.offset(SCALED_EPSILON); - m_bbox_external = get_extents(m_boundaries_external); - m_bbox_external.offset(SCALED_EPSILON); + BoundingBox bbox(get_extents(m_boundaries)); + bbox.offset(SCALED_EPSILON); + BoundingBox bbox_external = get_extents(m_boundaries_external); + bbox_external.offset(SCALED_EPSILON); - m_grid.set_bbox(m_bbox); + m_bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_bbox_external = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); + + m_grid.set_bbox(bbox); + //FIXME 1mm grid? m_grid.create(m_boundaries, coord_t(scale_(1.))); - m_grid_external.set_bbox(m_bbox_external); + m_grid_external.set_bbox(bbox_external); + //FIXME 1mm grid? m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index d33311a90..bdae775a1 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -47,9 +47,9 @@ private: // Collection of boundaries used for detection of crossing perimetrs for travels outside object Polygons m_boundaries_external; // Bounding box of m_boundaries - BoundingBox m_bbox; + BoundingBoxf m_bbox; // Bounding box of m_boundaries_external - BoundingBox m_bbox_external; + BoundingBoxf m_bbox_external; EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid_external; }; diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index a00dd802b..03f7ff59c 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -18,13 +18,6 @@ void MultiPoint::scale(double factor_x, double factor_y) } } -void MultiPoint::translate(double x, double y) -{ - Vector v(x, y); - for (Point &pt : points) - pt += v; -} - void MultiPoint::translate(const Point &v) { for (Point &pt : points) diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 653e59cd8..b7a5ab684 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -28,7 +28,7 @@ public: MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } void scale(double factor); void scale(double factor_x, double factor_y); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } void translate(const Point &vector); void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 75d236a54..962e2e04d 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -62,7 +62,6 @@ set(XS_XSP_FILES ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp - ${XSP_DIR}/MotionPlanner.xsp ${XSP_DIR}/PerimeterGenerator.xsp ${XSP_DIR}/PlaceholderParser.xsp ${XSP_DIR}/Point.xsp From 060f1d48c1c5ca08fe2011a2c250b86c5d386278 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 20 Nov 2020 11:56:40 +0100 Subject: [PATCH 029/225] Little more refactoring. --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 17 +++++++++-------- src/libslic3r/libslic3r.h | 8 +++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index f757a268e..666069014 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -297,7 +297,7 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, // Append the first intersection into the path size_t left_idx = intersection_first.line_idx; - size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1; // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the // appended point will be inside the polygon and not on the polygon border. @@ -306,7 +306,7 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, // Check if intersection line also exit the boundary polygon if (it_second_r != it_last_item) { // Transform reverse iterator to forward - auto it_second = (it_second_r.base() - 1); + auto it_second = it_second_r.base() - 1; // The exit point from the boundary polygon const Intersection &intersection_second = *it_second; Lines border_lines = boundaries[intersection_first.border_idx].lines(); @@ -315,12 +315,12 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, // Append the path around the border into the path if (shortest_direction == Direction::Forward) for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + line_idx = line_idx + 1 < int(border_lines.size()) ? line_idx + 1 : 0) result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); else for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) + line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(border_lines.size()) - 1) result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); // Append the farthest intersection into the path @@ -523,6 +523,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & // Trim the travel line by the bounding box. if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { // Travel line is completely or partially inside the bounding box. + //FIXME initialize m_boundaries / m_boundaries_external on demand? travel_intersection_count = use_external ? avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast(), endf.cast(), result_pl) : avoid_perimeters(m_boundaries, m_grid, startf.cast(), endf.cast(), result_pl); @@ -705,12 +706,12 @@ static ExPolygons get_boundary_external(const Layer &layer) auto [contours, holes] = split_expolygon(boundary); // Polygons in which is possible traveling without crossing perimeters of another object. // A convex hull allows removing unnecessary detour caused by following the boundary of the object. - ExPolygons result_boundary = union_ex( + ExPolygons result_boundary = //FIXME flip order of offset and convex_hull - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); + diff_ex(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset)); // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. - append(result_boundary, union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)))); + append(result_boundary, diff_ex(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); return union_ex(result_boundary); } diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index a404d230d..e74005993 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -123,8 +123,10 @@ inline void append(std::vector& dest, std::vector&& src) { if (dest.empty()) dest = std::move(src); - else + else { + dest.resize(dest.size() + src.size()); std::move(std::begin(src), std::end(src), std::back_inserter(dest)); + } src.clear(); src.shrink_to_fit(); } @@ -164,7 +166,7 @@ inline std::unique_ptr make_unique(Args&&... args) { // Variant of std::lower_bound() with compare predicate, but without the key. // This variant is very useful in case that the T type is large or it does not even have a public constructor. template -ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key) +ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_than_key) { ForwardIt it; typename std::iterator_traits::difference_type count, step; @@ -174,7 +176,7 @@ ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKey it = first; step = count / 2; std::advance(it, step); - if (lower_thank_key(*it)) { + if (lower_than_key(*it)) { first = ++it; count -= step + 1; } From 606db666fc6ff25db5cf04b080824e83480d5927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 20 Nov 2020 14:22:24 +0100 Subject: [PATCH 030/225] Fix missing include --- src/libslic3r/GCode.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 47da80b6c..5c7370e0b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -10,6 +10,7 @@ #include "ShortestPath.hpp" #include "Print.hpp" #include "Utils.hpp" +#include "ClipperUtils.hpp" #include "libslic3r.h" #include From 003ccf388d3cf777929b649e47b96d4c71650fc0 Mon Sep 17 00:00:00 2001 From: Paul Arden Date: Sun, 22 Nov 2020 18:21:59 +1100 Subject: [PATCH 031/225] Add a new macro function `random(min,max)` which will return a random number in the range specified by the parameters. The type of parameters defines the type of the output, if the inputs are doubles then result will be a double and if the inputs are int the output will be in. Uses C++11 random functionality, compiler compatibility on Linux should be checked. Fixes #4001. --- src/libslic3r/PlaceholderParser.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 0434e3a0a..0548cc5c3 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef _MSC_VER #include // provides **_environ #else @@ -495,9 +496,22 @@ namespace client static void leq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', true ); } static void geq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', true ); } + // Random number generators + static int random_int(int min, int max) { + thread_local static std::mt19937 engine(std::random_device{}()); + thread_local static std::uniform_int_distribution dist; + return dist(engine, decltype(dist)::param_type{ min, max }); + } + static double random_double(double min, double max) { + thread_local static std::mt19937 engine(std::random_device{}()); + thread_local static std::uniform_real_distribution dist; + return dist(engine, decltype(dist)::param_type{ min, max }); + } + enum Function2ParamsType { FUNCTION_MIN, FUNCTION_MAX, + FUNCTION_RANDOM, }; // Store the result into param1. static void function_2params(expr ¶m1, expr ¶m2, Function2ParamsType fun) @@ -510,6 +524,7 @@ namespace client switch (fun) { case FUNCTION_MIN: d = std::min(param1.as_d(), param2.as_d()); break; case FUNCTION_MAX: d = std::max(param1.as_d(), param2.as_d()); break; + case FUNCTION_RANDOM: d = random_double(param1.as_d(), param2.as_d()); break; default: param1.throw_exception("Internal error: invalid function"); } param1.data.d = d; @@ -519,6 +534,7 @@ namespace client switch (fun) { case FUNCTION_MIN: i = std::min(param1.as_i(), param2.as_i()); break; case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break; + case FUNCTION_RANDOM: i = random_int(param1.as_i(), param2.as_i()); break; default: param1.throw_exception("Internal error: invalid function"); } param1.data.i = i; @@ -528,6 +544,7 @@ namespace client // Store the result into param1. static void min(expr ¶m1, expr ¶m2) { function_2params(param1, param2, FUNCTION_MIN); } static void max(expr ¶m1, expr ¶m2) { function_2params(param1, param2, FUNCTION_MAX); } + static void random(expr ¶m1, expr ¶m2) { function_2params(param1, param2, FUNCTION_RANDOM); } static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) { @@ -1173,6 +1190,8 @@ namespace client [ px::bind(&expr::min, _val, _2) ] | (kw["max"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') [ px::bind(&expr::max, _val, _2) ] + | (kw["random"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') + [ px::bind(&expr::random, _val, _2) ] | (kw["int"] > '(' > unary_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] @@ -1209,6 +1228,7 @@ namespace client ("false") ("min") ("max") + ("random") ("not") ("or") ("true"); From 55c282d85dc0f1f9fc0ede41bccda7cf7d692799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 29 Nov 2020 13:58:36 +0100 Subject: [PATCH 032/225] Revamp of implementation of the avoid crossing perimeters algorithm. The strategy for the avoid crossing perimeters algorithm has been redesigned. But external travels (travel between objects or supports) have not been solved yet. For these travels is used a direct path between two points. Much of the code has been reworked, which leads to significant speedup compared to the previous implementation. Also, several potential bugs have been fixed. --- .../GCode/AvoidCrossingPerimeters.cpp | 957 ++++++++++++++---- .../GCode/AvoidCrossingPerimeters.hpp | 31 +- src/libslic3r/libslic3r.h | 2 +- 3 files changed, 771 insertions(+), 219 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 666069014..ff33813cf 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -29,20 +29,23 @@ struct Intersection size_t line_idx; // Point of intersection. Point point; + // Distance from the first point in the corresponding boundary + float distance; }; // Finding all intersections of a set of contours with a line segment. struct AllIntersectionsVisitor { - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) - : grid(grid), intersections(intersections) - {} + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) : grid(grid), intersections(intersections) + { + intersection_set.reserve(intersections.capacity()); + } - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, - std::vector &intersections, - const Line &travel_line) + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections, const Line &travel_line) : grid(grid), intersections(intersections), travel_line(travel_line) - {} + { + intersection_set.reserve(intersections.capacity()); + } void reset() { intersection_set.clear(); @@ -70,16 +73,52 @@ struct AllIntersectionsVisitor std::unordered_set, boost::hash>> intersection_set; }; +// Visitor to check for any collision of a line segment with any contour stored inside the edge_grid. +struct FirstIntersectionVisitor +{ + explicit FirstIntersectionVisitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; +}; + +// point_idx is the index from which is different vertex is searched. template static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) { assert(point_idx < polygon.size()); - auto line_idx = int(point_idx); - //FIXME endless loop if all points are equal to point? + // Solve case when vertex on passed index point_idx is different that pass point. This helps the following code keep simple. + if (point != polygon.points[point_idx]) + return polygon.points[point_idx]; + + auto line_idx = (int(point_idx) + 1) % int(polygon.points.size()); + assert(line_idx != int(point_idx)); if constexpr (forward) - for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); + for (; point == polygon.points[line_idx] && line_idx != int(point_idx); line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); else - for (; point == polygon.points[line_idx]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); + for (; point == polygon.points[line_idx] && line_idx != int(point_idx); line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); + assert(point != polygon.points[line_idx]); return polygon.points[line_idx]; } @@ -97,8 +136,8 @@ static Vec2d three_points_inward_normal(const Point &left, const Point &middle, // Compute normal of the polygon's vertex in an inward direction static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { - const size_t left_idx = point_idx == 0 ? polygon.size() - 1 : point_idx - 1; - const size_t right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1; + const size_t left_idx = prev_idx_modulo(point_idx, polygon.points); + const size_t right_idx = next_idx_modulo(point_idx, polygon.points); const Point &middle = polygon.points[point_idx]; const Point &left = find_first_different_vertex(polygon, left_idx, middle); const Point &right = find_first_different_vertex(polygon, right_idx, middle); @@ -128,14 +167,6 @@ static Polyline to_polyline(const std::vector &travel) return result; } -static double travel_length(const std::vector &travel) { - double total_length = 0; - for (size_t idx = 1; idx < travel.size(); ++idx) - total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); - - return total_length; -} - // #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT @@ -169,69 +200,43 @@ static void export_travel_to_svg(const Polygons &boundary, // Returns a direction of the shortest path along the polygon boundary enum class Direction { Forward, Backward }; -static Direction get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) +// Returns a direction of the shortest path along the polygon boundary +static Direction get_shortest_direction(const AvoidCrossingPerimeters::Boundary &boundary, + const Intersection &intersection_first, + const Intersection &intersection_second, + float contour_length) { - double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); - double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); + assert(intersection_first.border_idx == intersection_second.border_idx); + const Polygon &poly = boundary.boundaries[intersection_first.border_idx]; + float dist_first = intersection_first.distance; + float dist_second = intersection_second.distance; - auto cyclic_index = [&lines](int index) { - if (index >= int(lines.size())) - index = 0; - else if (index < 0) - index = int(lines.size()) - 1; + assert(dist_first >= 0.f && dist_first <= contour_length); + assert(dist_second >= 0.f && dist_second <= contour_length); - return index; - }; + bool reversed = false; + if (dist_first > dist_second) { + std::swap(dist_first, dist_second); + reversed = true; + } + float total_length_forward = dist_second - dist_first; + float total_length_backward = dist_first + contour_length - dist_second; + if (reversed) std::swap(total_length_forward, total_length_backward); - for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1)) - total_length_forward += lines[line_idx].length(); + total_length_forward -= (intersection_first.point - poly[intersection_first.line_idx]).cast().norm(); + total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast().norm(); - for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1)) - total_length_backward += lines[line_idx].length(); + total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast().norm(); + total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast().norm(); - total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); - total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); - - return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; + if (total_length_forward < total_length_backward) return Direction::Forward; + return Direction::Backward; } // Straighten the travel path as long as it does not collide with the contours stored in edge_grid. -static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, const std::vector &travel) +static std::vector simplify_travel(const AvoidCrossingPerimeters::Boundary &boundary, const std::vector &travel) { - // Visitor to check for a collision of a line segment with any contour stored inside the edge_grid. - struct Visitor - { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} - - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - + FirstIntersectionVisitor visitor(boundary.grid); std::vector simplified_path; simplified_path.reserve(travel.size()); simplified_path.emplace_back(travel.front()); @@ -253,7 +258,7 @@ static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, } visitor.pt_next = &travel[point_idx_2].point; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + boundary.grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); // Check if deleting point causes crossing a boundary if (!visitor.intersect) { next = travel[point_idx_2]; @@ -268,18 +273,634 @@ static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, } // Called by avoid_perimeters() and by simplify_travel_heuristics(). -static size_t avoid_perimeters_inner(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, +static size_t avoid_perimeters_inner(const GCode &gcodegen, const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, std::vector &result_out) { + const Polygons &boundaries = boundary.boundaries; + const EdgeGrid::Grid &edge_grid = boundary.grid; // Find all intersections between boundaries and the line segment, sort them along the line segment. std::vector intersections; { + intersections.reserve(boundaries.size()); AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); edge_grid.visit_cells_intersecting_line(start, end, visitor); Vec2d dir = (end - start).cast(); + for (Intersection &intersection : intersections) + intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx]; + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + } + + std::vector result; + result.push_back({start, -1}); + + auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) { + const Polygon &poly = boundary.boundaries[intersection.border_idx]; + Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast(); + Vec2d intersection_vec = (intersection.point - start).cast(); + return poly_line.normalized().dot(intersection_vec.normalized()) >= 0; + }; + + for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + // The entry point to the boundary polygon + const Intersection &intersection_first = *it_first; + if(!crossing_boundary_from_inside(start, intersection_first)) + continue; + // Skip the it_first from the search for the farthest exit point from the boundary polygon + auto it_last_item = std::make_reverse_iterator(it_first) - 1; + // Search for the farthest intersection different from it_first but with the same border_idx + auto it_second_r = std::find_if(intersections.rbegin(), it_last_item, [&intersection_first](const Intersection &intersection) { + return intersection_first.border_idx == intersection.border_idx; + }); + + // Append the first intersection into the path + size_t left_idx = intersection_first.line_idx; + size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1; + // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the + // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the + // appended point will be inside the polygon and not on the polygon border. + result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + + // Check if intersection line also exit the boundary polygon + if (it_second_r != it_last_item) { + // Transform reverse iterator to forward + auto it_second = it_second_r.base() - 1; + // The exit point from the boundary polygon + const Intersection &intersection_second = *it_second; + Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second, + boundary.boundaries_params[intersection_first.border_idx].back()); + // Append the path around the border into the path + if (shortest_direction == Direction::Forward) + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); + line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].size()) ? line_idx + 1 : 0) + result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + else + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); + line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].size()) - 1) + result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + + // Append the farthest intersection into the path + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx)}); + // Skip intersections in between + it_first = it_second; + } + } + + result.push_back({end, -1}); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + if (! intersections.empty()) + result = simplify_travel(boundary, result); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + append(result_out, std::move(result)); + return intersections.size(); +} + +// Called by AvoidCrossingPerimeters::travel_to() +static size_t avoid_perimeters(const GCode &gcodegen, const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + Polyline &result_out) +{ + // Travel line is completely or partially inside the bounding box. + std::vector path; + size_t num_intersections = avoid_perimeters_inner(gcodegen, boundary, start, end, path); + result_out = to_polyline(path); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + return num_intersections; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() and AvoidCrossingPerimeters::travel_to() +// FIXME Lukas H.: Maybe similar approach could also be used for ExPolygon::contains() +static bool any_expolygon_contains(const ExPolygons &ex_polygons, + const std::vector &ex_polygons_bboxes, + const EdgeGrid::Grid &grid_lslice, + const Line &travel) +{ + assert(ex_polygons.size() == ex_polygons_bboxes.size()); + if(!grid_lslice.bbox().contains(travel.a) || !grid_lslice.bbox().contains(travel.b)) + return false; + + FirstIntersectionVisitor visitor(grid_lslice); + visitor.pt_current = &travel.a; + visitor.pt_next = &travel.b; + grid_lslice.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + if (!visitor.intersect) { + for (const ExPolygon &ex_polygon : ex_polygons) { + const BoundingBox &bbox = ex_polygons_bboxes[&ex_polygon - &ex_polygons.front()]; + if (bbox.contains(travel.a) && bbox.contains(travel.b) && ex_polygon.contains(travel.a)) + return true; + } + } + return false; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() +static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vector &ex_polygons_bboxes, const EdgeGrid::Grid &grid_lslice, const Polyline &travel) +{ + assert(ex_polygons.size() == ex_polygons_bboxes.size()); + if(std::any_of(travel.points.begin(), travel.points.end(), [&grid_lslice](const Point &point) { return !grid_lslice.bbox().contains(point); })) + return false; + + FirstIntersectionVisitor visitor(grid_lslice); + bool any_intersection = false; + for (size_t line_idx = 1; line_idx < travel.size(); ++line_idx) { + visitor.pt_current = &travel.points[line_idx - 1]; + visitor.pt_next = &travel.points[line_idx]; + grid_lslice.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + any_intersection = visitor.intersect; + if (any_intersection) break; + } + + if (!any_intersection) { + for (const ExPolygon &ex_polygon : ex_polygons) { + const BoundingBox &bbox = ex_polygons_bboxes[&ex_polygon - &ex_polygons.front()]; + if (std::all_of(travel.points.begin(), travel.points.end(), [&bbox](const Point &point) { return bbox.contains(point); }) && + ex_polygon.contains(travel.points.front())) + return true; + } + } + return false; +} + +static bool need_wipe(const GCode &gcodegen, + const EdgeGrid::Grid &grid_lslice, + const Line &original_travel, + const Polyline &result_travel, + const size_t intersection_count) +{ + const ExPolygons &lslices = gcodegen.layer()->lslices; + const std::vector &lslices_bboxes = gcodegen.layer()->lslices_bboxes; + bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; + bool wipe_needed = false; + + // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely + // outside the object. + if (intersection_count > 0) { + // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. + // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. + if (z_lift_enabled) { + if (any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, original_travel)) { + // Check if original_travel and result_travel are not same. + // If both are the same, then it is possible to skip testing of result_travel + wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && + !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel); + } else { + wipe_needed = true; + } + } else { + wipe_needed = !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel); + } + } + + return wipe_needed; +} + +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const PrintObject &print_object) +{ + std::vector printing_extruders = print_object.object_extruders(); + assert(!printing_extruders.empty()); + float avg_extruder = 0; + for(unsigned int extruder_id : printing_extruders) + avg_extruder += scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)); + avg_extruder /= printing_extruders.size(); + return avg_extruder; +} + +// called by get_boundary() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const LayerRegion *layer_region : layer.regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + +// Adds points around all vertices so that the offset affects only small sections around these vertices. +static void resample_polygon(Polygon &polygon, double dist_from_vertex) +{ + Points resampled_poly; + resampled_poly.reserve(3 * polygon.size()); + resampled_poly.emplace_back(polygon.first_point()); + for (size_t pt_idx = 1; pt_idx < polygon.size(); ++pt_idx) { + const Point &p1 = polygon[pt_idx - 1]; + const Point &p2 = polygon[pt_idx]; + double line_length = (p2 - p1).cast().norm(); + Vector line_vec = ((p2 - p1).cast().normalized() * dist_from_vertex).cast(); + if (line_length > 2 * dist_from_vertex) { + resampled_poly.emplace_back(p1 + line_vec); + resampled_poly.emplace_back(p2 - line_vec); + } + resampled_poly.emplace_back(polygon[pt_idx]); + } + polygon.points = std::move(resampled_poly); +} + +static void resample_expolygon(ExPolygon &ex_polygon, double dist_from_vertex) +{ + resample_polygon(ex_polygon.contour, dist_from_vertex); + for (Polygon &polygon : ex_polygon.holes) resample_polygon(polygon, dist_from_vertex); +} + +static void resample_expolygons(ExPolygons &ex_polygons, double dist_from_vertex) +{ + for (ExPolygon &ex_poly : ex_polygons) resample_expolygon(ex_poly, dist_from_vertex); +} + +static void precompute_polygon_distances(const Polygon &polygon, std::vector &polygon_distances_out) +{ + polygon_distances_out.assign(polygon.size() + 1, 0.f); + for (size_t point_idx = 1; point_idx < polygon.size(); ++point_idx) + polygon_distances_out[point_idx] = polygon_distances_out[point_idx - 1] + (polygon[point_idx].cast() - polygon[point_idx - 1].cast()).norm(); + polygon_distances_out.back() = polygon_distances_out[polygon.size() - 1] + (polygon.last_point().cast() - polygon.first_point().cast()).norm(); +} + +static void precompute_expolygon_distances(const ExPolygon &ex_polygon, std::vector> &expolygon_distances_out) +{ + expolygon_distances_out.assign(ex_polygon.holes.size() + 1, std::vector()); + precompute_polygon_distances(ex_polygon.contour, expolygon_distances_out.front()); + for (size_t hole_idx = 0; hole_idx < ex_polygon.holes.size(); ++hole_idx) + precompute_polygon_distances(ex_polygon.holes[hole_idx], expolygon_distances_out[hole_idx + 1]); +} + +// It is highly based on the function contour_distance2 from the ElephantFootCompensation.cpp +static std::vector contour_distance(const EdgeGrid::Grid &grid, + const std::vector &poly_distances, + const size_t contour_idx, + const Polygon &polygon, + double compensation, + double search_radius) +{ + assert(! polygon.empty()); + assert(polygon.size() >= 2); + + std::vector out; + + if (polygon.size() > 2) + { + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const size_t contour_idx, const std::vector &polygon_distances, double dist_same_contour_accept, double dist_same_contour_reject) : + grid(grid), idx_contour(contour_idx), contour(*grid.contours()[contour_idx]), boundary_parameters(polygon_distances), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {} + + void init(const Points &contour, const Point &apoint) + { + this->idx_point = &apoint - contour.data(); + this->point = apoint; + this->found = false; + this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); + this->distance = std::numeric_limits::max(); + } + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = this->grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + std::pair segment = this->grid.segment(*it_contour_and_segment); + const Vec2d v = (segment.second - segment.first).cast(); + const Vec2d va = (this->point - segment.first).cast(); + const double l2 = v.squaredNorm(); // avoid a sqrt + const double t = (l2 == 0.0) ? 0. : clamp(0., 1., va.dot(v) / l2); + // Closest point from this->point to the segment. + const Vec2d foot = segment.first.cast() + t * v; + const Vec2d bisector = foot - this->point.cast(); + const double dist = bisector.norm(); + + if ((!this->found || dist < this->distance) && this->dir_inside.dot(bisector) > 0) { + bool accept = true; + if (it_contour_and_segment->first == idx_contour) { + // Complex case: The closest segment originates from the same contour as the starting point. + // Reject the closest point if its distance along the contour is reasonable compared to the current contour bisector + // (this->pt, foot). + const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first]; + double param_lo = boundary_parameters[this->idx_point]; + double param_hi = t * sqrt(l2); + double param_end = boundary_parameters.back(); + const size_t ipt = it_contour_and_segment->second; + if (ipt + 1 < ipts.size()) + param_hi += boundary_parameters[ipt > 0 ? ipt - 1 : 0]; + if (param_lo > param_hi) + std::swap(param_lo, param_hi); + assert(param_lo > -SCALED_EPSILON && param_lo <= param_end + SCALED_EPSILON); + assert(param_hi > -SCALED_EPSILON && param_hi <= param_end + SCALED_EPSILON); + double dist_along_contour = std::min(param_hi - param_lo, param_lo + param_end - param_hi); + if (dist_along_contour < dist_same_contour_accept) + accept = false; + else if (dist < dist_same_contour_reject + SCALED_EPSILON) { + // this->point is close to foot. This point will only be accepted if the path along the contour is significantly + // longer than the bisector. That is, the path shall not bulge away from the bisector too much. + // Bulge is estimated by 0.6 of the circle circumference drawn around the bisector. + // Test whether the contour is convex or concave. + bool inside = (t == 0.) ? this->inside_corner(ipts, ipt, this->point) : + (t == 1.) ? this->inside_corner(ipts, ipt + 1 == ipts.size() ? 0 : ipt + 1, this->point) : + this->left_of_segment(ipts, ipt, this->point); + accept = inside && dist_along_contour > 0.6 * M_PI * dist; + } + } + if (accept && (!this->found || dist < this->distance)) { + // Simple case: Just measure the shortest distance. + this->distance = dist; + this->found = true; + } + } + } + // Continue traversing the grid. + return true; + } + + const EdgeGrid::Grid &grid; + const size_t idx_contour; + const Points &contour; + + const std::vector &boundary_parameters; + const double dist_same_contour_accept; + const double dist_same_contour_reject; + + size_t idx_point; + Point point; + // Direction inside the contour from idx_point, not normalized. + Vec2d dir_inside; + bool found; + double distance; + + private: + static Vec2d dir_inside_at_point(const Points &contour, size_t i) + { + size_t iprev = prev_idx_modulo(i, contour); + size_t inext = next_idx_modulo(i, contour); + Vec2d v1 = (contour[i] - contour[iprev]).cast(); + Vec2d v2 = (contour[inext] - contour[i]).cast(); + return Vec2d(-v1.y() - v2.y(), v1.x() + v2.x()); + } + + static bool inside_corner(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) + { + const Vec2d pt = pt_oposite.cast(); + size_t iprev = prev_idx_modulo(i, contour); + size_t inext = next_idx_modulo(i, contour); + Vec2d v1 = (contour[i] - contour[iprev]).cast(); + Vec2d v2 = (contour[inext] - contour[i]).cast(); + bool left_of_v1 = cross2(v1, pt - contour[iprev].cast()) > 0.; + bool left_of_v2 = cross2(v2, pt - contour[i].cast()) > 0.; + return cross2(v1, v2) > 0 ? left_of_v1 && left_of_v2 : // convex corner + left_of_v1 || left_of_v2; // concave corner + } + + static bool left_of_segment(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) + { + const Vec2d pt = pt_oposite.cast(); + size_t inext = next_idx_modulo(i, contour); + Vec2d v = (contour[inext] - contour[i]).cast(); + return cross2(v, pt - contour[i].cast()) > 0.; + } + } visitor(grid, contour_idx, poly_distances, 0.5 * compensation * M_PI, search_radius); + + out.reserve(polygon.size()); + Point radius_vector(search_radius, search_radius); + for (const Point &pt : polygon.points) { + visitor.init(polygon.points, pt); + grid.visit_cells_intersecting_box(BoundingBox(pt - radius_vector, pt + radius_vector), visitor); + out.emplace_back(float(visitor.found ? std::min(visitor.distance, search_radius) : search_radius)); + } + } + + return out; +} + +// Polygon offset which ensures that if a polygon breaks up into several separate parts, the original polygon will be used in these places. +static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset, double min_contour_width = scale_(0.001)) +{ + double search_radius = 2. * (offset + min_contour_width); + ExPolygons ex_poly_result = ex_polygons; + resample_expolygons(ex_poly_result, offset / 2); + + for (ExPolygon &ex_poly : ex_poly_result) { + BoundingBox bbox(get_extents(ex_poly)); + bbox.offset(SCALED_EPSILON); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(ex_poly, coord_t(0.7 * search_radius)); + + std::vector> ex_poly_distances; + precompute_expolygon_distances(ex_poly, ex_poly_distances); + + std::vector> offsets; + offsets.reserve(ex_poly.holes.size() + 1); + for (size_t idx_contour = 0; idx_contour <= ex_poly.holes.size(); ++idx_contour) { + const Polygon &poly = (idx_contour == 0) ? ex_poly.contour : ex_poly.holes[idx_contour - 1]; + assert(poly.is_counter_clockwise() == (idx_contour == 0)); + std::vector distances = contour_distance(grid, ex_poly_distances[idx_contour], idx_contour, poly, offset, search_radius); + for (float &distance : distances) { + if (distance < min_contour_width) + distance = 0.f; + else if (distance > min_contour_width + 2. * offset) + distance = - float(offset); + else + distance = - (distance - float(min_contour_width)) / 2.f; + } + offsets.emplace_back(distances); + } + + ExPolygons offset_ex_poly = variable_offset_inner_ex(ex_poly, offsets); + // If variable_offset_inner_ex produces empty result, then original ex_polygon is used + if (offset_ex_poly.size() == 1) { + ex_poly = std::move(offset_ex_poly.front()); + } else if (offset_ex_poly.size() > 1) { + // fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces + // tiny artefacts polygons, so these artefacts are removed. + double max_area = offset_ex_poly.front().area(); + size_t max_area_idx = 0; + for (size_t poly_idx = 1; poly_idx < offset_ex_poly.size(); ++poly_idx) { + double area = offset_ex_poly[poly_idx].area(); + if (max_area < area) { + max_area = area; + max_area_idx = poly_idx; + } + } + ex_poly = std::move(offset_ex_poly[max_area_idx]); + } + } + return ex_poly_result; +} + +// called by AvoidCrossingPerimeters::travel_to() +static ExPolygons get_boundary(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); + + ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + } + + return boundary; +} + +static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) +{ + boundary->boundaries_params.assign(boundary->boundaries.size(), std::vector()); + for (size_t poly_idx = 0; poly_idx < boundary->boundaries.size(); ++poly_idx) + precompute_polygon_distances(boundary->boundaries[poly_idx], boundary->boundaries_params[poly_idx]); +} + +// Plan travel, which avoids perimeter crossings by following the boundaries of the layer. +Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +{ + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = m_use_external_mp || m_use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + const Point start = gcodegen.last_pos() + scaled_origin; + const Point end = point + scaled_origin; + const Line travel(start, end); + + Polyline result_pl; + size_t travel_intersection_count = 0; + Vec2d startf = start.cast(); + Vec2d endf = end .cast(); + + if (!use_external && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { + // Initialize m_internal only when it is necessary. + if (m_internal.boundaries.empty()) { + m_internal.boundaries_params.clear(); + m_internal.boundaries = to_polygons(get_boundary(*gcodegen.layer())); + + BoundingBox bbox(get_extents(m_internal.boundaries)); + bbox.offset(SCALED_EPSILON); + m_internal.bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_internal.grid.set_bbox(bbox); + // FIXME 1mm grid? + m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.))); + init_boundary_distances(&m_internal); + } + + // Trim the travel line by the bounding box. + if (Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { + travel_intersection_count = avoid_perimeters(gcodegen, m_internal, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; + } else { + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; + } + } else { + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; + } + + double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); + if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) + result_pl = {start, end}; + + if (use_external) { + result_pl.translate(-scaled_origin); + *could_be_wipe_disabled = false; + } else + *could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count); + + return result_pl; +} + +// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** + +void AvoidCrossingPerimeters::init_layer(const Layer &layer) +{ + m_internal.boundaries.clear(); + m_internal.boundaries_params.clear(); + BoundingBox bbox_slice(get_extents(layer.lslices)); + bbox_slice.offset(SCALED_EPSILON); + + m_grid_lslice.set_bbox(bbox_slice); + //FIXME 1mm grid? + m_grid_lslice.create(layer.lslices, coord_t(scale_(1.))); +} + +#if 0 +static double travel_length(const std::vector &travel) { + double total_length = 0; + for (size_t idx = 1; idx < travel.size(); ++idx) + total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); + + return total_length; +} + +// Called by avoid_perimeters() and by simplify_travel_heuristics(). +static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + std::vector &result_out) +{ + const Polygons &boundaries = boundary.boundaries; + const EdgeGrid::Grid &edge_grid = boundary.grid; + // Find all intersections between boundaries and the line segment, sort them along the line segment. + std::vector intersections; + { + intersections.reserve(boundaries.size()); + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); + edge_grid.visit_cells_intersecting_line(start, end, visitor); + Vec2d dir = (end - start).cast(); + for (Intersection &intersection : intersections) + intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx]; std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); } @@ -309,18 +930,17 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, auto it_second = it_second_r.base() - 1; // The exit point from the boundary polygon const Intersection &intersection_second = *it_second; - Lines border_lines = boundaries[intersection_first.border_idx].lines(); - - Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); + Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second, + boundary.boundaries_params[intersection_first.border_idx].back()); // Append the path around the border into the path if (shortest_direction == Direction::Forward) for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = line_idx + 1 < int(border_lines.size()) ? line_idx + 1 : 0) + line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].size()) ? line_idx + 1 : 0) result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); else for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(border_lines.size()) - 1) + line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].size()) - 1) result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); // Append the farthest intersection into the path @@ -343,7 +963,7 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ if (! intersections.empty()) - result = simplify_travel(edge_grid, result); + result = simplify_travel(boundary, result); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { @@ -357,13 +977,12 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, return intersections.size(); } -static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries) +static std::vector simplify_travel_heuristics(const AvoidCrossingPerimeters::Boundary &boundary, + const std::vector &travel) { std::vector simplified_path; std::vector intersections; - AllIntersectionsVisitor visitor(edge_grid, intersections); + AllIntersectionsVisitor visitor(boundary.grid, intersections); simplified_path.reserve(travel.size()); simplified_path.emplace_back(travel.front()); for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { @@ -399,7 +1018,7 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid visitor.reset(); visitor.travel_line.a = current.point; visitor.travel_line.b = possible_new_next.point; - edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); + boundary.grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); if (!intersections.empty()) { Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast(); std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); @@ -412,7 +1031,7 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid continue; std::vector possible_shortcut; - avoid_perimeters_inner(boundaries, edge_grid, current.point, possible_new_next.point, possible_shortcut); + avoid_perimeters_inner(boundary, current.point, possible_new_next.point, possible_shortcut); double shortcut_travel = travel_length(possible_shortcut); if (path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by) { new_path_shorter_by = path_length - shortcut_travel; @@ -437,19 +1056,18 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid } // Called by AvoidCrossingPerimeters::travel_to() -static size_t avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, +static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, Polyline &result_out) { // Travel line is completely or partially inside the bounding box. std::vector path; - size_t num_intersections = avoid_perimeters_inner(boundaries, edge_grid, start, end, path); + size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path); if (num_intersections) { - path = simplify_travel_heuristics(edge_grid, path, boundaries); + path = simplify_travel_heuristics(boundary, path); std::reverse(path.begin(), path.end()); - path = simplify_travel_heuristics(edge_grid, path, boundaries); + path = simplify_travel_heuristics(boundary, path); std::reverse(path.begin(), path.end()); } @@ -465,48 +1083,6 @@ static size_t avoid_perimeters(const Polygons &boundaries, return num_intersections; } -// Check if anyone of ExPolygons contains whole travel. -// called by need_wipe() -template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) -{ - //FIXME filter by bounding boxes! - for (const ExPolygon &ex_polygon : ex_polygons) - if (ex_polygon.contains(travel)) - return true; - return false; -} - -static bool need_wipe(const GCode &gcodegen, - const ExPolygons &slice, - const Line &original_travel, - const Polyline &result_travel, - const size_t intersection_count) -{ - bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; - bool wipe_needed = false; - - // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely - // outside the object. - if (intersection_count > 0) { - // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. - // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. - if (z_lift_enabled) { - if (any_expolygon_contains(slice, original_travel)) { - // Check if original_travel and result_travel are not same. - // If both are the same, then it is possible to skip testing of result_travel - wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && - !any_expolygon_contains(slice, result_travel); - } else { - wipe_needed = true; - } - } else { - wipe_needed = !any_expolygon_contains(slice, result_travel); - } - } - - return wipe_needed; -} - // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { @@ -521,14 +1097,13 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & Vec2d startf = start.cast(); Vec2d endf = end .cast(); // Trim the travel line by the bounding box. - if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { + if (Geometry::liang_barsky_line_clipping(startf, endf, (use_external ? m_external : m_internal).bbox)) { // Travel line is completely or partially inside the bounding box. //FIXME initialize m_boundaries / m_boundaries_external on demand? - travel_intersection_count = use_external ? - avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast(), endf.cast(), result_pl) : - avoid_perimeters(m_boundaries, m_grid, startf.cast(), endf.cast(), result_pl); - result_pl.points.front() = start; - result_pl.points.back() = end; + travel_intersection_count = avoid_perimeters((use_external ? m_external : m_internal), startf.cast(), endf.cast(), + result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; } else { // Travel line is completely outside the bounding box. result_pl = {start, end}; @@ -544,41 +1119,11 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !need_wipe(gcodegen, m_slice, travel, result_pl, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count); return result_pl; } -// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** - -// called by get_perimeter_spacing() / get_perimeter_spacing_external() -static inline float get_default_perimeter_spacing(const Print &print) -{ - //FIXME better use extruders printing this PrintObject or this Print? - //FIXME maybe better use an average of printing extruders? - const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; - return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); -} - -// called by get_boundary() -static float get_perimeter_spacing(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - //FIXME not all regions are printing. Collect only non-empty regions? - for (const LayerRegion *layer_region : layer.regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++ regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - // called by get_boundary_external() static float get_perimeter_spacing_external(const Layer &layer) { @@ -587,18 +1132,18 @@ static float get_perimeter_spacing_external(const Layer &layer) for (const PrintObject *object : layer.object()->print()->objects()) //FIXME with different layering, layers on other objects will not be found at this object's print_z. // Search an overlap of layers? - if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) - //FIXME not all regions are printing. Collect only non-empty regions? - for (const LayerRegion *layer_region : l->regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++ regions_count; - } + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } assert(perimeter_spacing >= 0.f); if (regions_count != 0) perimeter_spacing /= float(regions_count); else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); return perimeter_spacing; } @@ -651,9 +1196,7 @@ static ExPolygons get_boundary(const Layer &layer) auto [contours, holes] = split_expolygon(boundary); // Add an outer boundary to avoid crossing perimeters from supports ExPolygons outer_boundary = union_ex( - //FIXME flip order of offset and convex_hull - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); + diff(offset(Geometry::convex_hull(contours), 2.f * perimeter_spacing), offset(contours, perimeter_spacing + perimeter_offset))); result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); @@ -690,7 +1233,7 @@ static ExPolygons get_boundary_external(const Layer &layer) ExPolygons polygons_per_obj; //FIXME with different layering, layers on other objects will not be found at this object's print_z. // Search an overlap of layers? - if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) for (const LayerRegion *layer_region : l->regions()) for (const Surface &surface : layer_region->slices.surfaces) polygons_per_obj.emplace_back(surface.expolygon); @@ -707,9 +1250,7 @@ static ExPolygons get_boundary_external(const Layer &layer) // Polygons in which is possible traveling without crossing perimeters of another object. // A convex hull allows removing unnecessary detour caused by following the boundary of the object. ExPolygons result_boundary = - //FIXME flip order of offset and convex_hull - diff_ex(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset)); + diff_ex(offset(Geometry::convex_hull(contours), 2.f * perimeter_spacing),offset(contours, perimeter_spacing + perimeter_offset)); // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. append(result_boundary, diff_ex(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); return union_ex(result_boundary); @@ -717,31 +1258,35 @@ static ExPolygons get_boundary_external(const Layer &layer) void AvoidCrossingPerimeters::init_layer(const Layer &layer) { - m_slice.clear(); - m_boundaries.clear(); - m_boundaries_external.clear(); + m_internal.boundaries.clear(); + m_external.boundaries.clear(); - for (const LayerRegion *layer_region : layer.regions()) - //FIXME making copies? - append(m_slice, (ExPolygons) layer_region->slices); + m_internal.boundaries = to_polygons(get_boundary(layer)); + m_external.boundaries = to_polygons(get_boundary_external(layer)); - m_boundaries = to_polygons(get_boundary(layer)); - m_boundaries_external = to_polygons(get_boundary_external(layer)); - - BoundingBox bbox(get_extents(m_boundaries)); + BoundingBox bbox(get_extents(m_internal.boundaries)); bbox.offset(SCALED_EPSILON); - BoundingBox bbox_external = get_extents(m_boundaries_external); + BoundingBox bbox_external = get_extents(m_external.boundaries); bbox_external.offset(SCALED_EPSILON); + BoundingBox bbox_slice(get_extents(layer.lslices)); + bbox_slice.offset(SCALED_EPSILON); - m_bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); - m_bbox_external = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); + m_internal.bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_external.bbox = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); - m_grid.set_bbox(bbox); - //FIXME 1mm grid? - m_grid.create(m_boundaries, coord_t(scale_(1.))); - m_grid_external.set_bbox(bbox_external); - //FIXME 1mm grid? - m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); + m_internal.grid.set_bbox(bbox); + //FIX1ME 1mm grid? + m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.))); + m_external.grid.set_bbox(bbox_external); + //FIX1ME 1mm grid? + m_external.grid.create(m_external.boundaries, coord_t(scale_(1.))); + m_grid_lslice.set_bbox(bbox_slice); + //FIX1ME 1mm grid? + m_grid_lslice.create(layer.lslices, coord_t(scale_(1.))); + + init_boundary_distances(&m_internal); + init_boundary_distances(&m_external); } +#endif } // namespace Slic3r diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index bdae775a1..1ed1f0026 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -32,6 +32,17 @@ public: Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); + struct Boundary { + // Collection of boundaries used for detection of crossing perimeters for travels + Polygons boundaries; + // Bounding box of boundaries + BoundingBoxf bbox; + // Precomputed distances of all points in boundaries + std::vector> boundaries_params; + // Used for detection of intersection between line and any polygon from boundaries + EdgeGrid::Grid grid; + }; + private: bool m_use_external_mp { false }; // just for the next travel move @@ -40,18 +51,14 @@ private: // we enable it by default for the first travel move in print bool m_disabled_once { true }; - // Slice of layer with elephant foot compensation - ExPolygons m_slice; - // Collection of boundaries used for detection of crossing perimetrs for travels inside object - Polygons m_boundaries; - // Collection of boundaries used for detection of crossing perimetrs for travels outside object - Polygons m_boundaries_external; - // Bounding box of m_boundaries - BoundingBoxf m_bbox; - // Bounding box of m_boundaries_external - BoundingBoxf m_bbox_external; - EdgeGrid::Grid m_grid; - EdgeGrid::Grid m_grid_external; + // Used for detection of line or polyline is inside of any polygon. + EdgeGrid::Grid m_grid_lslice; + // Store all needed data for travels inside object + Boundary m_internal; +#if 0 + // Store all needed data for travels outside object + Boundary m_external; +#endif }; } // namespace Slic3r diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index e74005993..fce401a93 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -124,7 +124,7 @@ inline void append(std::vector& dest, std::vector&& src) if (dest.empty()) dest = std::move(src); else { - dest.resize(dest.size() + src.size()); + dest.reserve(dest.size() + src.size()); std::move(std::begin(src), std::end(src), std::back_inserter(dest)); } src.clear(); From f16e8f1a722962c37f88a0cb0fde36580accc6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 29 Nov 2020 14:00:34 +0100 Subject: [PATCH 033/225] Fixed uninitialized variable in ElephantFootCompensation --- src/libslic3r/ElephantFootCompensation.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp index c11157627..1e50ade5a 100644 --- a/src/libslic3r/ElephantFootCompensation.cpp +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -254,11 +254,12 @@ std::vector contour_distance2(const EdgeGrid::Grid &grid, const size_t id grid(grid), idx_contour(idx_contour), contour(*grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {} void init(const Points &contour, const Point &apoint) { - this->idx_point = &apoint - contour.data(); - this->point = apoint; - this->found = false; - this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); - } + this->idx_point = &apoint - contour.data(); + this->point = apoint; + this->found = false; + this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); + this->distance = std::numeric_limits::max(); + } bool operator()(coord_t iy, coord_t ix) { // Called with a row and colum of the grid cell, which is intersected by a line. From dd97eaa8125220f81fdd8a41c6ccddb1a743b068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 29 Nov 2020 17:26:02 +0100 Subject: [PATCH 034/225] Fixed case when lslices in Layer is empty --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index ff33813cf..ec5418348 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -491,7 +491,7 @@ static inline float get_default_perimeter_spacing(const PrintObject &print_objec assert(!printing_extruders.empty()); float avg_extruder = 0; for(unsigned int extruder_id : printing_extruders) - avg_extruder += scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)); + avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id))); avg_extruder /= printing_extruders.size(); return avg_extruder; } @@ -817,7 +817,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & Vec2d startf = start.cast(); Vec2d endf = end .cast(); - if (!use_external && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { + if (!use_external && !gcodegen.layer()->lslices.empty() && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { // Initialize m_internal only when it is necessary. if (m_internal.boundaries.empty()) { m_internal.boundaries_params.clear(); From f917b83706cff2c3ccaedde222542242aefd731f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 29 Nov 2020 17:29:11 +0100 Subject: [PATCH 035/225] Fixed compiler warnings --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index ec5418348..7ce3cebc3 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -122,10 +122,6 @@ static Point find_first_different_vertex(const Polygon &polygon, const size_t po return polygon.points[line_idx]; } -//FIXME will be in Point.h in the master -template -inline Eigen::Matrix perp(const Eigen::MatrixBase>& v) { return Eigen::Matrix(-v.y(), v.x()); } - static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); @@ -223,11 +219,11 @@ static Direction get_shortest_direction(const AvoidCrossingPerimeters::Boundary float total_length_backward = dist_first + contour_length - dist_second; if (reversed) std::swap(total_length_forward, total_length_backward); - total_length_forward -= (intersection_first.point - poly[intersection_first.line_idx]).cast().norm(); - total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast().norm(); + total_length_forward -= (intersection_first.point - poly[intersection_first.line_idx]).cast().norm(); + total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast().norm(); - total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast().norm(); - total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast().norm(); + total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast().norm(); + total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast().norm(); if (total_length_forward < total_length_backward) return Direction::Forward; return Direction::Backward; From 98055de28c29a1e572163f601b6cea335ebeb2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Dec 2020 09:43:21 +0100 Subject: [PATCH 036/225] External paths avoid crossing perimeters of holes --- .../GCode/AvoidCrossingPerimeters.cpp | 133 +++++++++++------- .../GCode/AvoidCrossingPerimeters.hpp | 8 +- 2 files changed, 91 insertions(+), 50 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 7ce3cebc3..8262c3857 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -511,6 +511,27 @@ static float get_perimeter_spacing(const Layer &layer) return perimeter_spacing; } +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + // Adds points around all vertices so that the offset affects only small sections around these vertices. static void resample_polygon(Polygon &polygon, double dist_from_vertex) { @@ -765,13 +786,9 @@ static ExPolygons get_boundary(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; - size_t polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - polygons_count += layer_region->slices.surfaces.size(); - - ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); + ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); // Collect all top layers that will not be crossed. - polygons_count = 0; + size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) for (const Surface &surface : layer_region->fill_surfaces.surfaces) if (surface.is_top()) ++polygons_count; @@ -790,6 +807,35 @@ static ExPolygons get_boundary(const Layer &layer) return boundary; } +// called by AvoidCrossingPerimeters::travel_to() +static Polygons get_boundary_external(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + Polygons boundary; + // Collect all holes for all printed objects and their instances, which will be printed at the same time as passed "layer". + for (const PrintObject *object : layer.object()->print()->objects()) { + Polygons polygons_per_obj; + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const ExPolygon &island : l->lslices) append(polygons_per_obj, island.holes); + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + append(boundary, polygons_per_obj); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift); + } + } + + // Used offset_ex for cases when another object will be in the hole of another polygon + boundary = to_polygons(offset_ex(boundary, perimeter_offset)); + // Reverse all polygons for making normals point from the polygon out. + for (Polygon &poly : boundary) + poly.reverse(); + + return boundary; +} + static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) { boundary->boundaries_params.assign(boundary->boundaries.size(), std::vector()); @@ -797,6 +843,20 @@ static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) precompute_polygon_distances(boundary->boundaries[poly_idx], boundary->boundaries_params[poly_idx]); } +static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons) +{ + boundary->clear(); + boundary->boundaries = std::move(boundary_polygons); + + BoundingBox bbox(get_extents(boundary->boundaries)); + bbox.offset(SCALED_EPSILON); + boundary->bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + boundary->grid.set_bbox(bbox); + // FIXME 1mm grid? + boundary->grid.create(boundary->boundaries, coord_t(scale_(1.))); + init_boundary_distances(boundary); +} + // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { @@ -815,30 +875,29 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & if (!use_external && !gcodegen.layer()->lslices.empty() && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { // Initialize m_internal only when it is necessary. - if (m_internal.boundaries.empty()) { - m_internal.boundaries_params.clear(); - m_internal.boundaries = to_polygons(get_boundary(*gcodegen.layer())); - - BoundingBox bbox(get_extents(m_internal.boundaries)); - bbox.offset(SCALED_EPSILON); - m_internal.bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); - m_internal.grid.set_bbox(bbox); - // FIXME 1mm grid? - m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.))); - init_boundary_distances(&m_internal); - } + if (m_internal.boundaries.empty()) + init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer()))); // Trim the travel line by the bounding box. if (Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { travel_intersection_count = avoid_perimeters(gcodegen, m_internal, startf.cast(), endf.cast(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; - } else { - // Travel line is completely outside the bounding box. - result_pl = {start, end}; - travel_intersection_count = 0; } - } else { + } else if(use_external) { + // Initialize m_external only when exist any external travel for the current layer. + if (m_external.boundaries.empty()) + init_boundary(&m_external, get_boundary_external(*gcodegen.layer())); + + // Trim the travel line by the bounding box. + if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { + travel_intersection_count = avoid_perimeters(gcodegen, m_external, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; + } + } + + if(result_pl.empty()) { // Travel line is completely outside the bounding box. result_pl = {start, end}; travel_intersection_count = 0; @@ -861,8 +920,9 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & void AvoidCrossingPerimeters::init_layer(const Layer &layer) { - m_internal.boundaries.clear(); - m_internal.boundaries_params.clear(); + m_internal.clear(); + m_external.clear(); + BoundingBox bbox_slice(get_extents(layer.lslices)); bbox_slice.offset(SCALED_EPSILON); @@ -1120,29 +1180,6 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & return result_pl; } -// called by get_boundary_external() -static float get_perimeter_spacing_external(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const PrintObject *object : layer.object()->print()->objects()) - //FIXME with different layering, layers on other objects will not be found at this object's print_z. - // Search an overlap of layers? - if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) - for (const LayerRegion *layer_region : l->regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++ regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()); - return perimeter_spacing; -} - // called by AvoidCrossingPerimeters::init_layer()->get_boundary()/get_boundary_external() static std::pair split_expolygon(const ExPolygons &ex_polygons) { diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 1ed1f0026..03c420a32 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -41,6 +41,12 @@ public: std::vector> boundaries_params; // Used for detection of intersection between line and any polygon from boundaries EdgeGrid::Grid grid; + + void clear() + { + boundaries.clear(); + boundaries_params.clear(); + } }; private: @@ -55,10 +61,8 @@ private: EdgeGrid::Grid m_grid_lslice; // Store all needed data for travels inside object Boundary m_internal; -#if 0 // Store all needed data for travels outside object Boundary m_external; -#endif }; } // namespace Slic3r From 569200eb992975fff3f41b115a73da7326758105 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 7 Dec 2020 16:14:40 +0100 Subject: [PATCH 037/225] Added "G-code thumbnails" parameter to the Printer Settings tab --- src/libslic3r/PrintConfig.cpp | 4 +- src/slic3r/GUI/Field.cpp | 51 +++++++++++++++++++++++++ src/slic3r/GUI/Field.hpp | 1 + src/slic3r/GUI/GUI.cpp | 2 +- src/slic3r/GUI/OptionsGroup.cpp | 26 +++++++------ src/slic3r/GUI/Search.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 7 +++- src/slic3r/GUI/UnsavedChangesDialog.cpp | 3 ++ 8 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 0e21abdf3..f9169fa73 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -63,8 +63,10 @@ void PrintConfigDef::init_common_params() def->set_default_value(new ConfigOptionString("")); def = this->add("thumbnails", coPoints); - def->label = L("Picture sizes to be stored into a .gcode and .sl1 files"); + def->label = L("G-code thumbnails"); + def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files"); def->mode = comExpert; + def->gui_type = "one_string"; def->set_default_value(new ConfigOptionPoints()); def = this->add("layer_height", coFloat); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index eb55e1535..7c70c1635 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "OG_CustomCtrl.hpp" @@ -52,6 +53,16 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/) return s; } +wxString get_thumbnails_string(const std::vector& values) +{ + wxString ret_str; + if (!values.empty()) + for (auto el : values) + ret_str += wxString::Format("%ix%i, ", int(el[0]), int(el[1])); + return ret_str; +} + + Field::~Field() { if (m_on_kill_focus) @@ -304,6 +315,43 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true m_value = std::string(str.ToUTF8().data()); break; } + + case coPoints: { + std::vector out_values; + str.Replace(" ", wxEmptyString, true); + if (!str.IsEmpty()) { + bool invalid_val = false; + wxStringTokenizer thumbnails(str, ","); + while (thumbnails.HasMoreTokens()) { + wxString token = thumbnails.GetNextToken(); + double x, y; + wxStringTokenizer thumbnail(token, "x"); + if (thumbnail.HasMoreTokens()) { + wxString x_str = thumbnail.GetNextToken(); + if (x_str.ToDouble(&x) && thumbnail.HasMoreTokens()) { + wxString y_str = thumbnail.GetNextToken(); + if (y_str.ToDouble(&y) && !thumbnail.HasMoreTokens()) { + out_values.push_back(Vec2d(x, y)); + continue; + } + } + } + invalid_val = true; + break; + } + + if (invalid_val) { + wxString text_value; + if (!m_value.empty()) + text_value = get_thumbnails_string(boost::any_cast>(m_value)); + set_value(text_value, true); + show_error(m_parent, format_wxstr(_L("Invalid input format. It must be represented like \"%1%\""),"XxY, XxY, ..." )); + } + } + + m_value = out_values; + break; } + default: break; } @@ -371,6 +419,9 @@ void TextCtrl::BUILD() { text_value = vec->get_at(m_opt_idx); break; } + case coPoints: + text_value = get_thumbnails_string(m_opt.get_default_value()->values); + break; default: break; } diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 9a15542de..5b01c92c9 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -37,6 +37,7 @@ using t_change = std::function; wxString double_to_string(double const value, const int max_precision = 4); +wxString get_thumbnails_string(const std::vector& values); class Field { protected: diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index dc7ffe152..dea226012 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -201,7 +201,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt } break; case coPoints:{ - if (opt_key.compare("bed_shape") == 0) { + if (opt_key == "bed_shape" || opt_key == "thumbnails") { config.option(opt_key)->values = boost::any_cast>(value); break; } diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 9da291984..d597d9cb2 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -25,20 +25,22 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id) { const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt) { // Check the gui_type field first, fall through // is the normal type. - if (opt.gui_type.compare("select") == 0) { - } else if (opt.gui_type.compare("select_open") == 0) { + if (opt.gui_type == "select") { + } else if (opt.gui_type == "select_open") { m_fields.emplace(id, std::move(Choice::Create(this->ctrl_parent(), opt, id))); - } else if (opt.gui_type.compare("color") == 0) { + } else if (opt.gui_type == "color") { m_fields.emplace(id, std::move(ColourPicker::Create(this->ctrl_parent(), opt, id))); - } else if (opt.gui_type.compare("f_enum_open") == 0 || - opt.gui_type.compare("i_enum_open") == 0 || - opt.gui_type.compare("i_enum_closed") == 0) { + } else if (opt.gui_type == "f_enum_open" || + opt.gui_type == "i_enum_open" || + opt.gui_type == "i_enum_closed") { m_fields.emplace(id, std::move(Choice::Create(this->ctrl_parent(), opt, id))); - } else if (opt.gui_type.compare("slider") == 0) { + } else if (opt.gui_type == "slider") { m_fields.emplace(id, std::move(SliderCtrl::Create(this->ctrl_parent(), opt, id))); - } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl - } else if (opt.gui_type.compare("legend") == 0) { // StaticText + } else if (opt.gui_type == "i_spin") { // Spinctrl + } else if (opt.gui_type == "legend") { // StaticText m_fields.emplace(id, std::move(StaticText::Create(this->ctrl_parent(), opt, id))); + } else if (opt.gui_type == "one_string") { + m_fields.emplace(id, std::move(TextCtrl::Create(this->ctrl_parent(), opt, id))); } else { switch (opt.type) { case coFloatOrPercent: @@ -837,9 +839,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config } if (config.option(opt_key)->values.empty()) ret = text_value; - else if (opt->gui_flags.compare("serialized") == 0) { + else if (opt->gui_flags == "serialized") { std::vector values = config.option(opt_key)->values; - if (!values.empty() && values[0].compare("") != 0) + if (!values.empty() && !values[0].empty()) for (auto el : values) text_value += el + ";"; ret = text_value; @@ -897,6 +899,8 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config case coPoints: if (opt_key == "bed_shape") ret = config.option(opt_key)->values; + if (opt_key == "thumbnails") + ret = get_thumbnails_string(config.option(opt_key)->values); else ret = config.option(opt_key)->get_at(idx); break; diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 1542067b3..d61fc05e0 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -94,7 +94,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty int cnt = 0; - if ( (type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER) && opt_key != "bed_shape") + if ( (type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER) && opt_key != "bed_shape" && opt_key != "thumbnails") switch (config->option(opt_key)->type()) { case coInts: change_opt_key(opt_key, config, cnt); break; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d7465c570..f7d72ae60 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -653,7 +653,7 @@ void TabPrinter::init_options_list() for (const auto opt_key : m_config->keys()) { - if (opt_key == "bed_shape") { + if (opt_key == "bed_shape" || opt_key == "thumbnails") { m_options_list.emplace(opt_key, m_opt_status_value); continue; } @@ -2173,6 +2173,11 @@ void TabPrinter::build_fff() optgroup = page->new_optgroup(L("Firmware")); optgroup->append_single_option_line("gcode_flavor"); + + option = optgroup->get_option("thumbnails"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + optgroup->append_single_option_line("silent_mode"); optgroup->append_single_option_line("remaining_times"); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index bb1abcf3d..db0e65da9 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -968,6 +968,9 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& BedShape shape(*config.option(opt_key)); return shape.get_full_name_with_params(); } + if (opt_key == "thumbnails") + return get_thumbnails_string(config.option(opt_key)->values); + Vec2d val = config.opt(opt_key)->get_at(opt_idx); return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str()); } From 780418435a58fc0367554db0df912cde37acc29f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Dec 2020 17:09:02 +0100 Subject: [PATCH 038/225] Fix of Custom profiles prevent the slicer from starting up #4996 Creative user made a profile inherit from itself. --- src/libslic3r/Preset.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 37575953d..cde517aca 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -991,7 +991,15 @@ const Preset* PresetCollection::get_preset_parent(const Preset& child) const if (it != m_presets.end()) preset = &(*it); } - return (preset == nullptr/* || preset->is_default */|| preset->is_external) ? nullptr : preset; + return + // not found + (preset == nullptr/* || preset->is_default */|| + // this should not happen, user profile should not derive from an external profile + preset->is_external || + // this should not happen, however people are creative, see GH #4996 + preset == &child) ? + nullptr : + preset; } // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist. From 042bfe6be4602445645cc240457dcc6efb46890a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 7 Dec 2020 17:29:35 +0100 Subject: [PATCH 039/225] Fixed localization for "Compatible presets" dialog. Relate to issue #4975 --- src/slic3r/GUI/Tab.cpp | 8 ++++---- src/slic3r/GUI/Tab.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index f7d72ae60..36ad2c02f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -109,14 +109,14 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) : m_compatible_printers.type = Preset::TYPE_PRINTER; m_compatible_printers.key_list = "compatible_printers"; m_compatible_printers.key_condition = "compatible_printers_condition"; - m_compatible_printers.dialog_title = _(L("Compatible printers")).ToUTF8(); - m_compatible_printers.dialog_label = _(L("Select the printers this profile is compatible with.")).ToUTF8(); + m_compatible_printers.dialog_title = _L("Compatible printers"); + m_compatible_printers.dialog_label = _L("Select the printers this profile is compatible with."); m_compatible_prints.type = Preset::TYPE_PRINT; m_compatible_prints.key_list = "compatible_prints"; m_compatible_prints.key_condition = "compatible_prints_condition"; - m_compatible_prints.dialog_title = _(L("Compatible print profiles")).ToUTF8(); - m_compatible_prints.dialog_label = _(L("Select the print profiles this profile is compatible with.")).ToUTF8(); + m_compatible_prints.dialog_title = _L("Compatible print profiles"); + m_compatible_prints.dialog_label = _L("Select the print profiles this profile is compatible with."); wxGetApp().tabs_list.push_back(this); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 5fb163aec..c9914e858 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -136,8 +136,8 @@ protected: ScalableButton *btn = nullptr; std::string key_list; // "compatible_printers" std::string key_condition; - std::string dialog_title; - std::string dialog_label; + wxString dialog_title; + wxString dialog_label; }; PresetDependencies m_compatible_printers; PresetDependencies m_compatible_prints; From b27e18c9705ff5eeea2d9be760b7018de095f613 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Dec 2020 17:52:37 +0100 Subject: [PATCH 040/225] Fix of fill_pattern handling in the GUI with 100% infill off-by-one #4999 --- src/slic3r/GUI/ConfigManipulation.cpp | 36 +++++++++++---------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 899a01369..5765897b8 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -2,6 +2,7 @@ #include "ConfigManipulation.hpp" #include "I18N.hpp" #include "GUI_App.hpp" +#include "format.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" @@ -184,30 +185,21 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con } if (config->option("fill_density")->value == 100) { - auto fill_pattern = config->option>("fill_pattern")->value; - std::string str_fill_pattern = ""; - t_config_enum_values map_names = config->option>("fill_pattern")->get_enum_values(); - for (auto it : map_names) { - if (fill_pattern == it.second) { - str_fill_pattern = it.first; - break; - } - } - if (!str_fill_pattern.empty()) { - const std::vector& external_fill_pattern = config->def()->get("top_fill_pattern")->enum_values; - bool correct_100p_fill = false; - for (const std::string& fill : external_fill_pattern) - { - if (str_fill_pattern == fill) - correct_100p_fill = true; - } + std::string fill_pattern = config->option>("fill_pattern")->serialize(); + const auto &top_fill_pattern_values = config->def()->get("top_fill_pattern")->enum_values; + bool correct_100p_fill = std::find(top_fill_pattern_values.begin(), top_fill_pattern_values.end(), fill_pattern) != top_fill_pattern_values.end(); + if (!correct_100p_fill) { // get fill_pattern name from enum_labels for using this one at dialog_msg - str_fill_pattern = _utf8(config->def()->get("fill_pattern")->enum_labels[fill_pattern]); - if (!correct_100p_fill) { - wxString msg_text = GUI::from_u8((boost::format(_utf8(L("The %1% infill pattern is not supposed to work at 100%% density."))) % str_fill_pattern).str()); + const ConfigOptionDef *fill_pattern_def = config->def()->get("fill_pattern"); + assert(fill_pattern_def != nullptr); + auto it_pattern = std::find(fill_pattern_def->enum_values.begin(), fill_pattern_def->enum_values.end(), fill_pattern); + assert(it_pattern != fill_pattern_def->enum_values.end()); + if (it_pattern != fill_pattern_def->enum_values.end()) { + wxString msg_text = GUI::format_wxstr(_L("The %1% infill pattern is not supposed to work at 100%% density."), + fill_pattern_def->enum_labels[it_pattern - fill_pattern_def->enum_values.begin()]); if (is_global_config) - msg_text += "\n\n" + _(L("Shall I switch to rectilinear fill pattern?")); - wxMessageDialog dialog(nullptr, msg_text, _(L("Infill")), + msg_text += "\n\n" + _L("Shall I switch to rectilinear fill pattern?"); + wxMessageDialog dialog(nullptr, msg_text, _L("Infill"), wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK) ); DynamicPrintConfig new_conf = *config; auto answer = dialog.ShowModal(); From 598ac290a1c4d305450252de7422c224966bb494 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 7 Dec 2020 18:00:10 +0100 Subject: [PATCH 041/225] Fix for bad wipe tower handling in arrangement fixes #5320 --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.hpp | 4 ++-- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 9 ++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6d6291d00..3bbc565e1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3779,7 +3779,7 @@ GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const m_config->opt_float("wipe_tower_y")); wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); const BoundingBoxf3& bb = vol->bounding_box(); - wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y()); + wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; break; } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 1329a8744..ca4efd296 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -665,8 +665,8 @@ public: class WipeTowerInfo { protected: Vec2d m_pos = {std::nan(""), std::nan("")}; - Vec2d m_bb_size = {0., 0.}; double m_rotation = 0.; + BoundingBoxf m_bb; friend class GLCanvas3D; public: @@ -677,7 +677,7 @@ public: inline const Vec2d& pos() const { return m_pos; } inline double rotation() const { return m_rotation; } - inline const Vec2d bb_size() const { return m_bb_size; } + inline const Vec2d bb_size() const { return m_bb.size(); } void apply_wipe_tower() const; }; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index ebe6515cb..0f17e6e9f 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -30,11 +30,10 @@ public: ArrangePolygon get_arrange_polygon() const { Polygon ap({ - {coord_t(0), coord_t(0)}, - {scaled(m_bb_size(X)), coord_t(0)}, - {scaled(m_bb_size)}, - {coord_t(0), scaled(m_bb_size(Y))}, - {coord_t(0), coord_t(0)}, + {scaled(m_bb.min)}, + {scaled(m_bb.max.x()), scaled(m_bb.min.y())}, + {scaled(m_bb.max)}, + {scaled(m_bb.min.x()), scaled(m_bb.max.y())} }); ArrangePolygon ret; From c7586e571363a3a79bc0039fa2ebef8449d61514 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Dec 2020 18:39:15 +0100 Subject: [PATCH 042/225] Fix of Command line slicing bad gcode #5029 SL1 file was exported with a .gcode suffix if the user did not provide output file name for SLA command line slicing. --- src/PrusaSlicer.cpp | 6 ++++++ src/libslic3r/GCode/PostProcessor.cpp | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 0a22305ec..f7350dace 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -484,6 +484,12 @@ int CLI::run(int argc, char **argv) if (printer_technology == ptFFF) { for (auto* mo : model.objects) fff_print.auto_assign_extruders(mo); + } else { + // The default for "output_filename_format" is good for FDM: "[input_filename_base].gcode" + // Replace it with a reasonable SLA default. + std::string &format = m_print_config.opt_string("output_filename_format", true); + if (format == static_cast(m_print_config.def()->get("output_filename_format")->default_value.get())->value) + format = "[input_filename_base].SL1"; } print->apply(model, m_print_config); std::string err = print->validate(); diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index e3131d591..9a66e743b 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -182,8 +182,10 @@ namespace Slic3r { void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config) { const auto* post_process = config.opt("post_process"); - assert(post_process); - if (post_process->values.empty()) + if (// likely running in SLA mode + post_process == nullptr || + // no post-processing script + post_process->values.empty()) return; // Store print configuration into environment variables. From 7e3d8c3142eebdb23c599c8f10efd1d662560a1b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 7 Dec 2020 18:39:55 +0100 Subject: [PATCH 043/225] Fixed localization for the name of Infill pattern (hot fix for the b27e18c9705ff5eeea2d9be760b7018de095f613) --- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5765897b8..185f9aa25 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -196,7 +196,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con assert(it_pattern != fill_pattern_def->enum_values.end()); if (it_pattern != fill_pattern_def->enum_values.end()) { wxString msg_text = GUI::format_wxstr(_L("The %1% infill pattern is not supposed to work at 100%% density."), - fill_pattern_def->enum_labels[it_pattern - fill_pattern_def->enum_values.begin()]); + _(fill_pattern_def->enum_labels[it_pattern - fill_pattern_def->enum_values.begin()])); if (is_global_config) msg_text += "\n\n" + _L("Shall I switch to rectilinear fill pattern?"); wxMessageDialog dialog(nullptr, msg_text, _L("Infill"), From acd178291034e0046517ebdadefcfaa6f3ae658b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 7 Dec 2020 19:04:09 +0100 Subject: [PATCH 044/225] In AboutDialog added "Copy Version Info" button (FR #4990) --- src/slic3r/GUI/AboutDialog.cpp | 12 ++++++++++++ src/slic3r/GUI/AboutDialog.hpp | 2 ++ 2 files changed, 14 insertions(+) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 4ded4ec92..e320dedfd 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -297,6 +297,11 @@ AboutDialog::AboutDialog() auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots); buttons->Insert(0, copy_rights_btn, 0, wxLEFT, 5); copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this); + + m_copy_version_btn_id = NewControlId(); + auto copy_version_btn = new wxButton(this, m_copy_version_btn_id, _L("Copy Version Info")); + buttons->Insert(1, copy_version_btn, 0, wxLEFT, 5); + copy_version_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyToClipboard, this); this->SetEscapeId(wxID_CLOSE); this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE); @@ -348,5 +353,12 @@ void AboutDialog::onCopyrightBtn(wxEvent &) dlg.ShowModal(); } +void AboutDialog::onCopyToClipboard(wxEvent&) +{ + wxTheClipboard->Open(); + wxTheClipboard->SetData(new wxTextDataObject(_L("Version") + " " + std::string(SLIC3R_VERSION))); + wxTheClipboard->Close(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/AboutDialog.hpp b/src/slic3r/GUI/AboutDialog.hpp index f1e26fde4..8774d8ce8 100644 --- a/src/slic3r/GUI/AboutDialog.hpp +++ b/src/slic3r/GUI/AboutDialog.hpp @@ -60,6 +60,7 @@ class AboutDialog : public DPIDialog wxHtmlWindow* m_html; wxStaticBitmap* m_logo; int m_copy_rights_btn_id { wxID_ANY }; + int m_copy_version_btn_id { wxID_ANY }; public: AboutDialog(); @@ -70,6 +71,7 @@ private: void onLinkClicked(wxHtmlLinkEvent &event); void onCloseDialog(wxEvent &); void onCopyrightBtn(wxEvent &); + void onCopyToClipboard(wxEvent&); }; } // namespace GUI From 2a44fbefd670809285ca0d1d6ce1b50e40411fe5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 7 Dec 2020 19:36:54 +0100 Subject: [PATCH 045/225] Don't ask about inches if we load project file (3mf or amf). #5006 --- src/slic3r/GUI/Plater.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2c9463968..f1c129118 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2399,18 +2399,18 @@ std::vector Plater::priv::load_files(const std::vector& input_ wxGetApp().sidebar().update_ui_from_settings(); }; - if (imperial_units) - convert_from_imperial_units(model); - else if (model.looks_like_imperial_units()) { - wxMessageDialog msg_dlg(q, format_wxstr(_L( - "Some object(s) in file %s looks like saved in inches.\n" - "Should I consider them as a saved in inches and convert them?"), from_path(filename)) + "\n", - _L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO); - if (msg_dlg.ShowModal() == wxID_YES) + if (!is_project_file) { + if (imperial_units) convert_from_imperial_units(model); - } + else if (model.looks_like_imperial_units()) { + wxMessageDialog msg_dlg(q, format_wxstr(_L( + "Some object(s) in file %s looks like saved in inches.\n" + "Should I consider them as a saved in inches and convert them?"), from_path(filename)) + "\n", + _L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO); + if (msg_dlg.ShowModal() == wxID_YES) + convert_from_imperial_units(model); + } - if (! is_project_file) { if (model.looks_like_multipart_object()) { wxMessageDialog msg_dlg(q, _L( "This file contains several objects positioned at multiple heights.\n" From ffea00454e51d98a5a6888f67f8d666f7600ca67 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Dec 2020 19:47:37 +0100 Subject: [PATCH 046/225] Fixed missing include. --- src/slic3r/Config/Snapshot.cpp | 21 +++++++++++++-------- src/slic3r/GUI/Field.cpp | 1 + src/slic3r/GUI/GUI_App.cpp | 15 +++++++++------ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 54d1dea57..900172d3e 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -264,12 +264,14 @@ bool Snapshot::equal_to_active(const AppConfig &app_config) const boost::filesystem::path path1 = data_dir / subdir; boost::filesystem::path path2 = snapshot_dir / subdir; std::vector files1, files2; - for (auto &dir_entry : boost::filesystem::directory_iterator(path1)) - if (Slic3r::is_ini_file(dir_entry)) - files1.emplace_back(dir_entry.path().filename().string()); - for (auto &dir_entry : boost::filesystem::directory_iterator(path2)) - if (Slic3r::is_ini_file(dir_entry)) - files2.emplace_back(dir_entry.path().filename().string()); + if (boost::filesystem::is_directory(path1)) + for (auto &dir_entry : boost::filesystem::directory_iterator(path1)) + if (Slic3r::is_ini_file(dir_entry)) + files1.emplace_back(dir_entry.path().filename().string()); + if (boost::filesystem::is_directory(path2)) + for (auto &dir_entry : boost::filesystem::directory_iterator(path2)) + if (Slic3r::is_ini_file(dir_entry)) + files2.emplace_back(dir_entry.path().filename().string()); std::sort(files1.begin(), files1.end()); std::sort(files2.begin(), files2.end()); if (files1 != files2) @@ -459,8 +461,11 @@ void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_confi boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; // Remove existing ini files and restore the ini files from the snapshot. for (const char *subdir : snapshot_subdirs) { - delete_existing_ini_files(data_dir / subdir); - copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir); + boost::filesystem::path src = snapshot_dir / subdir; + boost::filesystem::path dst = data_dir / subdir; + delete_existing_ini_files(dst); + if (boost::filesystem::is_directory(src)) + copy_config_dir_single_level(src, dst); } // Update AppConfig with the selections of the print / sla_print / filament / sla_material / printer profiles // and about the installed printer types and variants. diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7c70c1635..359d9d97e 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -5,6 +5,7 @@ #include "wxExtensions.hpp" #include "Plater.hpp" #include "MainFrame.hpp" +#include "format.hpp" #include "libslic3r/PrintConfig.hpp" diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 84232e2d3..74e6d3746 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1582,13 +1582,16 @@ void GUI_App::add_config_menu(wxMenuBar *menu) ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); dlg.ShowModal(); if (!dlg.snapshot_to_activate().empty()) { - if (!Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) + if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK); - app_config->set("on_snapshot", - Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); - preset_bundle->load_presets(*app_config); - // Load the currently selected preset into the GUI, update the preset selection box. - load_current_presets(); + try { + app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); + preset_bundle->load_presets(*app_config); + // Load the currently selected preset into the GUI, update the preset selection box. + load_current_presets(); + } catch (std::exception &ex) { + GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); + } } } break; From 3e22b39a42c911e18ac44e41c723b8699ed8fb87 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Dec 2020 21:10:20 +0100 Subject: [PATCH 047/225] Improved error handling when loading invalid preset bundle. Improves Import Configuration bundle can fail if profile names have spaces at the end #5291 --- src/libslic3r/PresetBundle.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index b020f874e..f98abcc19 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1095,7 +1095,11 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla namespace pt = boost::property_tree; pt::ptree tree; boost::nowide::ifstream ifs(path); - pt::read_ini(ifs, tree); + try { + pt::read_ini(ifs, tree); + } catch (const boost::property_tree::ini_parser::ini_parser_error &err) { + throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str()); + } const VendorProfile *vendor_profile = nullptr; if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { From 1874c42bb9383542a682b0434c9f995f9f90abe3 Mon Sep 17 00:00:00 2001 From: Richard Hartmann Date: Mon, 7 Dec 2020 22:04:57 +0100 Subject: [PATCH 048/225] PrintConfig.cpp: Fix grammar mistake --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f9169fa73..89ae92a9e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1983,7 +1983,7 @@ void PrintConfigDef::init_fff_params() "in order to remove any visible seam. This option requires a single perimeter, " "no infill, no top solid layers and no support material. You can still set " "any number of bottom solid layers as well as skirt/brim loops. " - "It won't work when printing more than an object."); + "It won't work when printing more than one single object."); def->set_default_value(new ConfigOptionBool(false)); def = this->add("standby_temperature_delta", coInt); From 6fe0b09a04f977486db083281d0082f87854a59d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Dec 2020 08:30:15 +0100 Subject: [PATCH 049/225] Fixes "Add instance" and "Remove instance" hotkeys don't work when object is selected via de object browser #5350 Captures the +- hotkeys in the object list to add / remove an instance. --- src/slic3r/GUI/GUI_ObjectList.cpp | 22 ++++++++++++++++++++-- src/slic3r/GUI/GUI_ObjectList.hpp | 2 ++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index fdec4a9c2..d9adcf60b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -191,7 +191,7 @@ ObjectList::ObjectList(wxWindow* parent) : // Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); { // Accelerators - wxAcceleratorEntry entries[8]; + wxAcceleratorEntry entries[10]; entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY); entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT); entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE); @@ -200,7 +200,9 @@ ObjectList::ObjectList(wxWindow* parent) : entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO); entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); - wxAcceleratorTable accel(8, entries); + entries[8].Set(wxACCEL_NORMAL, int('+'), wxID_ADD); + entries[9].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE); + wxAcceleratorTable accel(10, entries); SetAcceleratorTable(accel); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY); @@ -209,6 +211,8 @@ ObjectList::ObjectList(wxWindow* parent) : this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo(); }, wxID_UNDO); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instance(); }, wxID_ADD); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instance(); }, wxID_REMOVE); } #else //__WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX @@ -1092,6 +1096,16 @@ void ObjectList::redo() wxGetApp().plater()->redo(); } +void ObjectList::increase_instances() +{ + wxGetApp().plater()->increase_instances(1); +} + +void ObjectList::decrease_instances() +{ + wxGetApp().plater()->decrease_instances(1); +} + #ifndef __WXOSX__ void ObjectList::key_event(wxKeyEvent& event) { @@ -1116,6 +1130,10 @@ void ObjectList::key_event(wxKeyEvent& event) redo(); else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL)) undo(); + else if (event.GetUnicodeKey() == '+') + increase_instances(); + else if (event.GetUnicodeKey() == '-') + decrease_instances(); else event.Skip(); } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index c8bdd2d2c..011da676d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -257,6 +257,8 @@ public: bool paste_from_clipboard(); void undo(); void redo(); + void increase_instances(); + void decrease_instances(); void get_settings_choice(const wxString& category_name); void get_freq_settings_choice(const wxString& bundle_name); From c7aee35552a9a60233f65319c10e660c884a8a6d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Dec 2020 08:45:16 +0100 Subject: [PATCH 050/225] Merge of Fix proposal for crash on rectilinear #5399 --- src/libslic3r/Fill/FillRectilinear.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index e3860af46..f8393cf36 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -1237,7 +1237,7 @@ static void pinch_contours_insert_phony_outer_intersections(std::vector Date: Tue, 8 Dec 2020 10:11:11 +0100 Subject: [PATCH 051/225] Increase min height for the ObjectList. Related to the FR #5162 --- src/slic3r/GUI/GUI_App.cpp | 17 +++-------------- src/slic3r/GUI/GUI_ObjectList.cpp | 12 ++++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 1 + 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 74e6d3746..f2827be40 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -933,13 +933,7 @@ bool GUI_App::on_init_inner() load_current_presets(); mainframe->Show(true); - /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: - * change min hight of object list to the normal min value (15 * wxGetApp().em_unit()) - * after first whole Mainframe updating/layouting - */ - const int list_min_height = 15 * em_unit(); - if (obj_list()->GetMinSize().GetY() > list_min_height) - obj_list()->SetMinSize(wxSize(-1, list_min_height)); + obj_list()->set_min_height(); update_mode(); // update view mode after fix of the object_list size @@ -1150,13 +1144,8 @@ void GUI_App::recreate_GUI(const wxString& msg_name) mainframe->Show(true); dlg.Update(90, _L("Loading of a mode view") + dots); - /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: - * change min hight of object list to the normal min value (15 * wxGetApp().em_unit()) - * after first whole Mainframe updating/layouting - */ - const int list_min_height = 15 * em_unit(); - if (obj_list()->GetMinSize().GetY() > list_min_height) - obj_list()->SetMinSize(wxSize(-1, list_min_height)); + + obj_list()->set_min_height(); update_mode(); // #ys_FIXME_delete_after_testing Do we still need this ? diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d9adcf60b..576202594 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -258,6 +258,18 @@ ObjectList::~ObjectList() { } +void ObjectList::set_min_height() +{ + /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: + * change min hight of object list to the normal min value (35 * wxGetApp().em_unit()) + * after first whole Mainframe updating/layouting + */ + const int list_min_height = 35 * wxGetApp().em_unit(); + if (this->GetMinSize().GetY() > list_min_height) + this->SetMinSize(wxSize(-1, list_min_height)); +} + + void ObjectList::create_objects_ctrl() { /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 011da676d..aaea78339 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -207,6 +207,7 @@ public: ObjectList(wxWindow* parent); ~ObjectList(); + void set_min_height(); std::map CATEGORY_ICON; From ca2691c67cdfe264c23b9a7c4141e8a5e94d7ec6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Dec 2020 10:49:24 +0100 Subject: [PATCH 052/225] Fix for OSX --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 576202594..0229962ea 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -211,8 +211,8 @@ ObjectList::ObjectList(wxWindow* parent) : this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo(); }, wxID_UNDO); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instance(); }, wxID_ADD); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instance(); }, wxID_REMOVE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instances(); }, wxID_ADD); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instances(); }, wxID_REMOVE); } #else //__WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX From fa8a72b2a310b6420a89d913044379f1ed5df6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20North=20=5Bnor=C3=B0urlj=C3=B3sahvi=C3=B0a=5D?= Date: Tue, 8 Dec 2020 11:12:42 +0100 Subject: [PATCH 053/225] Reduced max print height for Anycubic Predator to 445mm --- resources/profiles/Anycubic.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index 3cf93824f..74457052a 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.5 +config_version = 0.0.6 # 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% @@ -1696,7 +1696,7 @@ printer_technology = FFF printer_variant = 0.4 thumbnails = 16x16,220x124 bed_shape = 188.779x16.516,186.621x32.9063,183.043x49.0462,178.072x64.8128,171.745x80.0862,164.112x94.75,155.229x108.693,145.165x121.808,133.997x133.997,121.808x145.165,108.693x155.229,94.75x164.112,80.0862x171.745,64.8128x178.072,49.0462x183.043,32.9063x186.621,16.516x188.779,1.16035e-14x189.5,-16.516x188.779,-32.9063x186.621,-49.0462x183.043,-64.8128x178.072,-80.0862x171.745,-94.75x164.112,-108.693x155.229,-121.808x145.165,-133.997x133.997,-145.165x121.808,-155.229x108.693,-164.112x94.75,-171.745x80.0862,-178.072x64.8128,-183.043x49.0462,-186.621x32.9063,-188.779x16.516,-189.5x2.32071e-14,-188.779x-16.516,-186.621x-32.9063,-183.043x-49.0462,-178.072x-64.8128,-171.745x-80.0862,-164.112x-94.75,-155.229x-108.693,-145.165x-121.808,-133.997x-133.997,-121.808x-145.165,-108.693x-155.229,-94.75x-164.112,-80.0862x-171.745,-64.8128x-178.072,-49.0462x-183.043,-32.9063x-186.621,-16.516x-188.779,-3.48106e-14x-189.5,16.516x-188.779,32.9063x-186.621,49.0462x-183.043,64.8128x-178.072,80.0862x-171.745,94.75x-164.112,108.693x-155.229,121.808x-145.165,133.997x-133.997,145.165x-121.808,155.229x-108.693,164.112x-94.75,171.745x-80.0862,178.072x-64.8128,183.043x-49.0462,186.621x-32.9063,188.779x-16.516,189.5x-4.64141e-14 -max_print_height = 450 +max_print_height = 445 z_offset = 0 single_extruder_multi_material = 0 gcode_flavor = reprap From bf3786be59e6a26a23912e45c2fdd2495e391515 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Dec 2020 11:43:00 +0100 Subject: [PATCH 054/225] ENABLE_RENDER_STATISTICS -> FPS averaged to last second --- src/slic3r/GUI/GLCanvas3D.cpp | 22 +++++++++------------- src/slic3r/GUI/GLCanvas3D.hpp | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6d6291d00..6671c76c9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1679,22 +1679,20 @@ void GLCanvas3D::render() if (wxGetApp().plater()->is_render_statistic_dialog_visible()) { ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - imgui.text("Last frame: "); + imgui.text("Last frame:"); ImGui::SameLine(); - imgui.text(std::to_string(m_render_stats.last_frame)); + long long average = m_render_stats.get_average(); + imgui.text(std::to_string(average)); ImGui::SameLine(); - imgui.text(" ms"); - imgui.text("FPS: "); + imgui.text("ms"); + imgui.text("FPS:"); ImGui::SameLine(); - imgui.text(std::to_string(static_cast(1000.0f / static_cast(m_render_stats.last_frame)))); -// imgui.text("Imgui FPS: "); -// ImGui::SameLine(); -// imgui.text(std::to_string(static_cast(ImGui::GetIO().Framerate))); + imgui.text(std::to_string((average == 0) ? 0 : static_cast(1000.0f / static_cast(average)))); ImGui::Separator(); - imgui.text("Compressed textures: "); + imgui.text("Compressed textures:"); ImGui::SameLine(); imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported"); - imgui.text("Max texture size: "); + imgui.text("Max texture size:"); ImGui::SameLine(); imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size())); imgui.end(); @@ -1707,8 +1705,6 @@ void GLCanvas3D::render() std::string tooltip; - - // Negative coordinate means out of the window, likely because the window was deactivated. // In that case the tooltip should be hidden. if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) { @@ -1745,7 +1741,7 @@ void GLCanvas3D::render() #if ENABLE_RENDER_STATISTICS auto end_time = std::chrono::high_resolution_clock::now(); - m_render_stats.last_frame = std::chrono::duration_cast(end_time - start_time).count(); + m_render_stats.add_frame(std::chrono::duration_cast(end_time - start_time).count()); #endif // ENABLE_RENDER_STATISTICS } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 1329a8744..b8be40401 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -320,11 +320,22 @@ class GLCanvas3D }; #if ENABLE_RENDER_STATISTICS - struct RenderStats + class RenderStats { - long long last_frame; + std::queue> m_frames; + long long m_curr_total{ 0 }; - RenderStats() : last_frame(0) {} + public: + void add_frame(long long frame) { + long long now = wxGetLocalTimeMillis().GetValue(); + if (!m_frames.empty() && now - m_frames.front().first > 1000) { + m_curr_total -= m_frames.front().second; + m_frames.pop(); + } + m_curr_total += frame; + m_frames.push({ now, frame }); + } + long long get_average() const { return m_frames.empty() ? 0 : m_curr_total / m_frames.size(); } }; #endif // ENABLE_RENDER_STATISTICS From 3134359505a8abf772872b4db279e1674c5307bb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Dec 2020 12:27:15 +0100 Subject: [PATCH 055/225] OSX specific: Fixed shortcut "Cmd+M" --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e621a262c..3109ae7c9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2487,7 +2487,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) break; #if ENABLE_CTRL_M_ON_WINDOWS +#ifdef __APPLE__ + case 'm': + case 'M': +#else /* __APPLE__ */ case WXK_CONTROL_M: +#endif /* __APPLE__ */ { #ifdef _WIN32 if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") { From 2d711d864658122b26aa03a1ec4d93f22339cf0f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 8 Dec 2020 12:31:17 +0100 Subject: [PATCH 056/225] Added missing includes for gcc --- src/libslic3r/PresetBundle.cpp | 1 + src/slic3r/GUI/AboutDialog.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f98abcc19..39d1f11c0 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -4,6 +4,7 @@ #include "libslic3r.h" #include "Utils.hpp" #include "Model.hpp" +#include "format.hpp" #include #include diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index e320dedfd..5152260f5 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -7,6 +7,8 @@ #include "MainFrame.hpp" #include "format.hpp" +#include + namespace Slic3r { namespace GUI { From 90248bffa5e3c4831f540a7c1bb1a9bd8f3e3f6e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Dec 2020 13:29:23 +0100 Subject: [PATCH 057/225] Parameter "G-code thumbnails": check value for the out of range --- src/slic3r/GUI/Field.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 359d9d97e..c6f6babae 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -322,6 +322,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true str.Replace(" ", wxEmptyString, true); if (!str.IsEmpty()) { bool invalid_val = false; + bool out_of_range_val = false; wxStringTokenizer thumbnails(str, ","); while (thumbnails.HasMoreTokens()) { wxString token = thumbnails.GetNextToken(); @@ -332,8 +333,12 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true if (x_str.ToDouble(&x) && thumbnail.HasMoreTokens()) { wxString y_str = thumbnail.GetNextToken(); if (y_str.ToDouble(&y) && !thumbnail.HasMoreTokens()) { - out_values.push_back(Vec2d(x, y)); - continue; + if (0 < x && x < 1000 && 0 < y && y < 1000) { + out_values.push_back(Vec2d(x, y)); + continue; + } + out_of_range_val = true; + break; } } } @@ -341,7 +346,15 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true break; } - if (invalid_val) { + if (out_of_range_val) { + wxString text_value; + if (!m_value.empty()) + text_value = get_thumbnails_string(boost::any_cast>(m_value)); + set_value(text_value, true); + show_error(m_parent, _L("Input value is out of range") + ); + } + else if (invalid_val) { wxString text_value; if (!m_value.empty()) text_value = get_thumbnails_string(boost::any_cast>(m_value)); From 5bd443cd1f84bf39d19ea5c1c818e74c10c1ba5a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Dec 2020 15:55:53 +0100 Subject: [PATCH 058/225] #4936 - Take toolpaths width and height from gcode decorations, if available --- src/libslic3r/GCode.cpp | 15 ++++ src/libslic3r/GCode.hpp | 10 +++ src/libslic3r/GCode/GCodeProcessor.cpp | 100 ++++++++++++++++++++++++- src/libslic3r/GCode/GCodeProcessor.hpp | 11 ++- src/libslic3r/GCode/WipeTower.cpp | 26 +++++-- src/libslic3r/Technologies.hpp | 7 ++ src/slic3r/GUI/GCodeViewer.cpp | 19 +++++ 7 files changed, 177 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 94b832dbf..24307d7d0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1014,9 +1014,14 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_last_height = 0.f; m_last_layer_z = 0.f; m_max_layer_z = 0.f; +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + m_last_width = 0.f; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_last_mm3_per_mm = 0.; +#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE m_last_width = 0.f; +#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING // How many times will be change_layer() called? @@ -2701,6 +2706,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; } +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + if (last_was_wipe_tower || m_last_width != path.width) { + m_last_width = path.width; + sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); + gcode += buf; + } +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + #if ENABLE_GCODE_VIEWER_DATA_CHECKING if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { m_last_mm3_per_mm = path.mm3_per_mm; @@ -2708,11 +2721,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; } +#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE if (last_was_wipe_tower || m_last_width != path.width) { m_last_width = path.width; sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); gcode += buf; } +#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 8a8154976..d8d352359 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -160,9 +160,14 @@ public: m_volumetric_speed(0), m_last_pos_defined(false), m_last_extrusion_role(erNone), +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + m_last_width(0.0f), +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_last_mm3_per_mm(0.0), +#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE m_last_width(0.0f), +#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING m_brim_done(false), m_second_layer_things_done(false), @@ -353,9 +358,14 @@ private: float m_last_height{ 0.0f }; float m_last_layer_z{ 0.0f }; float m_max_layer_z{ 0.0f }; +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + float m_last_width{ 0.0f }; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE #if ENABLE_GCODE_VIEWER_DATA_CHECKING double m_last_mm3_per_mm; +#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE float m_last_width{ 0.0f }; +#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING Point m_last_pos; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3db2a91f7..f36d3edb2 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -40,8 +40,13 @@ const std::string GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag = "; _ const float GCodeProcessor::Wipe_Width = 0.05f; const float GCodeProcessor::Wipe_Height = 0.05f; +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE +const std::string GCodeProcessor::Width_Tag = "WIDTH:"; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE #if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE const std::string GCodeProcessor::Width_Tag = "WIDTH:"; +#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:"; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -739,6 +744,10 @@ void GCodeProcessor::reset() m_feedrate = 0.0f; m_width = 0.0f; m_height = 0.0f; +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + m_forced_width = 0.0f; + m_forced_height = 0.0f; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE m_mm3_per_mm = 0.0f; m_fan_speed = 0.0f; @@ -1057,10 +1066,26 @@ void GCodeProcessor::process_tags(const std::string_view comment) return; } +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) { + // height tag + if (starts_with(comment, Height_Tag)) { + if (!parse_number(comment.substr(Height_Tag.size()), m_forced_height)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + return; + } + // width tag + if (starts_with(comment, Width_Tag)) { + if (!parse_number(comment.substr(Width_Tag.size()), m_forced_width)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + return; + } + } +#else if ((!m_producers_enabled || m_producer == EProducer::PrusaSlicer) && starts_with(comment, Height_Tag)) { // height tag - if (! parse_number(comment.substr(Height_Tag.size()), m_height)) + if (!parse_number(comment.substr(Height_Tag.size()), m_height)) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; return; } @@ -1073,6 +1098,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) return; } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE // color change tag if (starts_with(comment, Color_Change_Tag)) { @@ -1304,6 +1330,33 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment) } // geometry +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + // ; tool + std::string tag = " tool"; + pos = comment.find(tag); + if (pos == 0) { + const std::string_view data = comment.substr(pos + tag.length()); + std::string h_tag = "H"; + size_t h_start = data.find(h_tag); + size_t h_end = data.find_first_of(' ', h_start); + std::string w_tag = "W"; + size_t w_start = data.find(w_tag); + size_t w_end = data.find_first_of(' ', w_start); + if (h_start != data.npos) { + if (!parse_number(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end), m_forced_height)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + } + if (w_start != data.npos) { + if (!parse_number(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end), m_forced_width)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + } + + return true; + } + + // ; layer + tag = " layer"; +#else #if ENABLE_GCODE_VIEWER_DATA_CHECKING // ; tool std::string tag = " tool"; @@ -1331,6 +1384,7 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment) // ; layer std::string tag = " layer"; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE pos = comment.find(tag); if (pos == 0) { // skip lines "; layer end" @@ -1421,6 +1475,25 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string_view comment) } // geometry +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + // width + tag = "WIDTH:"; + pos = comment.find(tag); + if (pos != comment.npos) { + if (!parse_number(comment.substr(pos + tag.length()), m_forced_width)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + return true; + } + + // height + tag = "HEIGHT:"; + pos = comment.find(tag); + if (pos != comment.npos) { + if (!parse_number(comment.substr(pos + tag.length()), m_forced_height)) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + return true; + } +#else #if ENABLE_GCODE_VIEWER_DATA_CHECKING // width tag = "WIDTH:"; @@ -1440,6 +1513,7 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string_view comment) return true; } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE // layer pos = comment.find("LAYER:"); @@ -1656,6 +1730,20 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + if (m_forced_height > 0.0f) + m_height = m_forced_height; + else { + if (m_end_position[Z] > m_extruded_last_z + EPSILON) { + m_height = m_end_position[Z] - m_extruded_last_z; + m_extruded_last_z = m_end_position[Z]; + } + } + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_height_compare.update(m_height, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#else if ((m_producers_enabled && m_producer != EProducer::PrusaSlicer) || m_height == 0.0f) { if (m_end_position[Z] > m_extruded_last_z + EPSILON) { m_height = m_end_position[Z] - m_extruded_last_z; @@ -1665,10 +1753,17 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_extruded_last_z = m_end_position[Z]; } } +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + if (m_forced_width > 0.0f) + m_width = m_forced_width; + else if (m_extrusion_role == erExternalPerimeter) +#else if (m_extrusion_role == erExternalPerimeter) +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE // cross section: rectangle - m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (delta_xyz * m_height); + m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) // cross section: circle m_width = static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); @@ -1678,7 +1773,6 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // clamp width to avoid artifacts which may arise from wrong values of m_height m_width = std::min(m_width, 1.0f); -// m_width = std::min(m_width, 4.0f * m_height); #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_width_compare.update(m_width, m_extrusion_role); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 40e29b84f..6367215cf 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -84,8 +84,13 @@ namespace Slic3r { static const float Wipe_Width; static const float Wipe_Height; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE static const std::string Width_Tag; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE +#if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + static const std::string Width_Tag; +#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE static const std::string Mm3_Per_Mm_Tag; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -401,6 +406,10 @@ namespace Slic3r { float m_feedrate; // mm/s float m_width; // mm float m_height; // mm +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + float m_forced_width; // mm + float m_forced_height; // mm +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE float m_mm3_per_mm; float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index c70ef8ae0..0f72dc415 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -46,12 +46,12 @@ public: m_gcode += buf; sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str()); m_gcode += buf; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE || ENABLE_GCODE_VIEWER_DATA_CHECKING change_analyzer_line_width(line_width); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE || ENABLE_GCODE_VIEWER_DATA_CHECKING } -#if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE WipeTowerWriter& change_analyzer_line_width(float line_width) { // adds tag for analyzer: char buf[64]; @@ -59,6 +59,18 @@ public: m_gcode += buf; return *this; } +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + WipeTowerWriter& change_analyzer_line_width(float line_width) { + // adds tag for analyzer: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width); + m_gcode += buf; + return *this; + } +#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; @@ -862,12 +874,12 @@ void WipeTower::toolchange_Unload( const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm -#if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE || ENABLE_GCODE_VIEWER_DATA_CHECKING writer.append("; CP TOOLCHANGE UNLOAD\n") .change_analyzer_line_width(line_width); #else writer.append("; CP TOOLCHANGE UNLOAD\n"); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE || ENABLE_GCODE_VIEWER_DATA_CHECKING unsigned i = 0; // iterates through ramming_speed m_left_to_right = true; // current direction of ramming @@ -930,9 +942,9 @@ void WipeTower::toolchange_Unload( } } Vec2f end_of_ramming(writer.x(),writer.y()); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE || ENABLE_GCODE_VIEWER_DATA_CHECKING writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE || ENABLE_GCODE_VIEWER_DATA_CHECKING // Retraction: float old_x = writer.x(); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 74102b615..75b228155 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -91,4 +91,11 @@ #define ENABLE_PREVIEW_TYPE_CHANGE (1 && ENABLE_2_3_0_BETA2) +//=================== +// 2.3.0.beta3 techs +//=================== +#define ENABLE_2_3_0_BETA3 1 + +#define ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE (1 && ENABLE_2_3_0_BETA3) + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 63449a59a..1c3b556a7 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -103,6 +103,12 @@ void GCodeViewer::IBuffer::reset() bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const { +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + auto matches_percent = [](float value1, float value2, float max_percent) { + return std::abs(value2 - value1) / value1 <= max_percent; + }; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + switch (move.type) { case EMoveType::Tool_change: @@ -113,10 +119,17 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const case EMoveType::Unretract: case EMoveType::Extrude: { // use rounding to reduce the number of generated paths +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && + move.position[2] <= first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed && + height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) && + matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); +#else return type == move.type && move.position[2] <= first.position[2] && role == move.extrusion_role && height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) && feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == round_to_nearest(move.volumetric_rate(), 2) && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE } case EMoveType::Travel: { return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; @@ -143,9 +156,15 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi { Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, + round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, + move.volumetric_rate(), move.extruder_id, move.cp_color_id }); +#else paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, round_to_nearest(move.volumetric_rate(), 2), move.extruder_id, move.cp_color_id }); +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE } GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const From 661ad1735ba866c66f7f447297f14463e811d06a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Dec 2020 17:09:00 +0100 Subject: [PATCH 059/225] Changed layout for for the Preferences Dialog (related to #5381) + Fixed #5312 + Deleted unused now EVT_TAB_VALUE_CHANGED and EVT_TAB_PRESETS_CHANGED --- src/slic3r/GUI/MainFrame.cpp | 7 -- src/slic3r/GUI/OG_CustomCtrl.cpp | 11 ++- src/slic3r/GUI/OG_CustomCtrl.hpp | 4 +- src/slic3r/GUI/OptionsGroup.cpp | 2 +- src/slic3r/GUI/OptionsGroup.hpp | 2 + src/slic3r/GUI/Preferences.cpp | 145 ++++++++++++++++--------------- src/slic3r/GUI/Tab.cpp | 3 - src/slic3r/GUI/Tab.hpp | 4 - 8 files changed, 87 insertions(+), 91 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 843eff25b..f2f9db400 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -545,13 +545,6 @@ void MainFrame::init_tabpanel() wxGetApp().obj_list()->create_popup_menus(); - // The following event is emited by Tab implementation on config value change. - Bind(EVT_TAB_VALUE_CHANGED, &MainFrame::on_value_changed, this); // #ys_FIXME_to_delete - - // The following event is emited by Tab on preset selection, - // or when the preset's "modified" status changes. - Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this); // #ys_FIXME_to_delete - if (wxGetApp().is_editor()) create_preset_tabs(); diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index a8ead0aab..07b96755d 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -95,7 +95,7 @@ void OG_CustomCtrl::init_ctrl_lines() { wxSize label_sz = GetTextExtent(line.label); height = label_sz.y * (label_sz.GetWidth() > int(opt_group->label_width * m_em_unit) ? 2 : 1) + m_v_gap; - ctrl_lines.emplace_back(CtrlLine(height, this, line)); + ctrl_lines.emplace_back(CtrlLine(height, this, line, false, opt_group->staticbox)); } else int i = 0; @@ -387,11 +387,13 @@ void OG_CustomCtrl::sys_color_changed() OG_CustomCtrl::CtrlLine::CtrlLine( wxCoord height, OG_CustomCtrl* ctrl, const Line& og_line, - bool draw_just_act_buttons /* = false*/): + bool draw_just_act_buttons /* = false*/, + bool draw_mode_bitmap/* = true*/): height(height), ctrl(ctrl), og_line(og_line), - draw_just_act_buttons(draw_just_act_buttons) + draw_just_act_buttons(draw_just_act_buttons), + draw_mode_bitmap(draw_mode_bitmap) { for (size_t i = 0; i < og_line.get_options().size(); i++) { @@ -567,6 +569,9 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos) wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) { + if (!draw_mode_bitmap) + return ctrl->m_h_gap; + ConfigOptionMode mode = og_line.get_options()[0].opt.mode; const std::string& bmp_name = mode == ConfigOptionMode::comSimple ? "mode_simple" : mode == ConfigOptionMode::comAdvanced ? "mode_advanced" : "mode_expert"; diff --git a/src/slic3r/GUI/OG_CustomCtrl.hpp b/src/slic3r/GUI/OG_CustomCtrl.hpp index 798443f39..afbc6c930 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.hpp +++ b/src/slic3r/GUI/OG_CustomCtrl.hpp @@ -39,13 +39,15 @@ class OG_CustomCtrl :public wxPanel const Line& og_line; bool draw_just_act_buttons { false }; + bool draw_mode_bitmap { true }; bool is_visible { true }; bool is_focused { false }; CtrlLine( wxCoord height, OG_CustomCtrl* ctrl, const Line& og_line, - bool draw_just_act_buttons = false); + bool draw_just_act_buttons = false, + bool draw_mode_bitmap = true); ~CtrlLine() { ctrl = nullptr; } void correct_items_positions(); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index d597d9cb2..10774abe3 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -216,7 +216,7 @@ void OptionsGroup::activate_line(Line& line) bool is_legend_line = option_set.front().opt.gui_type == "legend"; if (!custom_ctrl && m_use_custom_ctrl) { - custom_ctrl = new OG_CustomCtrl(is_legend_line ? this->parent() : static_cast(this->stb), this); + custom_ctrl = new OG_CustomCtrl(is_legend_line || !staticbox ? this->parent() : static_cast(this->stb), this); if (is_legend_line) sizer->Add(custom_ctrl, 0, wxEXPAND | wxLEFT, wxOSX ? 0 : 10); else diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 48e4ba0e9..5bc2d4595 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -221,6 +221,8 @@ public: ConfigOptionsGroup( wxWindow* parent, const wxString& title, ModelConfig* config, bool is_tab_opt = false, column_t extra_clmn = nullptr) : OptionsGroup(parent, title, is_tab_opt, extra_clmn), m_config(&config->get()), m_modelconfig(config) {} + ConfigOptionsGroup( wxWindow* parent) : + OptionsGroup(parent, wxEmptyString, true, nullptr) {} const std::string& config_category() const throw() { return m_config_category; } const t_opt_map& opt_map() const throw() { return m_opt_map; } diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index a2d65d2d1..c2566f20f 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -4,6 +4,7 @@ #include "Plater.hpp" #include "I18N.hpp" #include "libslic3r/AppConfig.hpp" +#include namespace Slic3r { namespace GUI { @@ -18,11 +19,38 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent) : build(); } +static std::shared_ptrcreate_options_tab(const wxString& title, wxNotebook* tabs) +{ + wxPanel* tab = new wxPanel(tabs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + tabs->AddPage(tab, title); + tab->SetFont(wxGetApp().normal_font()); + + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->SetSizeHints(tab); + tab->SetSizer(sizer); + + std::shared_ptr optgroup = std::make_shared(tab); + optgroup->label_width = 40; + return optgroup; +} + +static void activate_options_tab(std::shared_ptr optgroup) +{ + optgroup->activate(); + optgroup->update_visibility(comSimple); + wxBoxSizer* sizer = static_cast(static_cast(optgroup->parent())->GetSizer()); + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 20); +} + void PreferencesDialog::build() { auto app_config = get_app_config(); - m_optgroup_general = std::make_shared(this, _L("General")); - m_optgroup_general->label_width = 40; + + wxNotebook* tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); + tabs->SetFont(wxGetApp().normal_font()); + + // Add "General" tab + m_optgroup_general = create_options_tab(_L("General"), tabs); m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset") m_values[opt_key] = boost::any_cast(value) ? "none" : "discard"; @@ -30,16 +58,6 @@ void PreferencesDialog::build() m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; - // TODO -// $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( -// opt_id = > 'version_check', -// type = > 'bool', -// label = > 'Check for updates', -// tooltip = > 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.', -// default = > $app_config->get("version_check") // 1, -// readonly = > !wxTheApp->have_version_check, -// )); - bool is_editor = wxGetApp().is_editor(); ConfigOptionDef def; @@ -146,17 +164,6 @@ void PreferencesDialog::build() option = Option(def, "single_instance"); m_optgroup_general->append_single_option_line(option); - /* // ysFIXME THis part is temporary commented - // The using of inches is implemented just for object's size and position - - def.label = L("Use inches instead of millimeters"); - def.type = coBool; - def.tooltip = L("Use inches instead of millimeters for the object's size"); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_inches") == "1" }); - option = Option(def, "use_inches"); - m_optgroup_general->append_single_option_line(option); - */ - def.label = L("Ask for unsaved changes when closing application"); def.type = coBool; def.tooltip = L("When closing the application, always ask for unsaved changes"); @@ -203,20 +210,20 @@ void PreferencesDialog::build() m_optgroup_general->append_single_option_line(option); #if ENABLE_CTRL_M_ON_WINDOWS -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) def.label = L("Enable support for legacy 3DConnexion devices"); def.type = coBool; def.tooltip = L("If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M"); def.set_default_value(new ConfigOptionBool{ app_config->get("use_legacy_3DConnexion") == "1" }); option = Option(def, "use_legacy_3DConnexion"); m_optgroup_general->append_single_option_line(option); -#endif // _WIN32 +#endif // _WIN32 || __APPLE__ #endif // ENABLE_CTRL_M_ON_WINDOWS - m_optgroup_general->activate(); + activate_options_tab(m_optgroup_general); - m_optgroup_camera = std::make_shared(this, _L("Camera")); - m_optgroup_camera->label_width = 40; + // Add "Camera" tab + m_optgroup_camera = create_options_tab(_L("Camera"), tabs); m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -242,11 +249,11 @@ void PreferencesDialog::build() option = Option(def, "reverse_mouse_wheel_zoom"); m_optgroup_camera->append_single_option_line(option); - m_optgroup_camera->activate(); + activate_options_tab(m_optgroup_camera); - m_optgroup_gui = std::make_shared(this, _L("GUI")); - m_optgroup_gui->label_width = 40; - m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + // Add "GUI" tab + m_optgroup_gui = create_options_tab(_L("GUI"), tabs); + m_optgroup_gui->m_on_change = [this, tabs](t_config_option_key opt_key, boost::any value) { if (opt_key == "suppress_hyperlinks") m_values[opt_key] = boost::any_cast(value) ? "1" : ""; else @@ -254,34 +261,12 @@ void PreferencesDialog::build() if (opt_key == "use_custom_toolbar_size") { m_icon_size_sizer->ShowItems(boost::any_cast(value)); + m_optgroup_gui->parent()->Layout(); + tabs->Layout(); this->layout(); } }; - if (is_editor) { - def.label = L("Show sidebar collapse/expand button"); - def.type = coBool; - def.tooltip = L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"); - def.set_default_value(new ConfigOptionBool{ app_config->get("show_collapse_button") == "1" }); - option = Option(def, "show_collapse_button"); - m_optgroup_gui->append_single_option_line(option); - - def.label = L("Use custom size for toolbar icons"); - def.type = coBool; - def.tooltip = L("If enabled, you can change size of toolbar icons manually."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" }); - option = Option(def, "use_custom_toolbar_size"); - m_optgroup_gui->append_single_option_line(option); - - def.label = L("Suppress to open hyperlink in browser"); - def.type = coBool; - def.tooltip = L("If enabled, the descriptions of configuration parameters in settings tabs woldn't work as hyperlinks. " - "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."); - def.set_default_value(new ConfigOptionBool{ app_config->get("suppress_hyperlinks") == "1" }); - option = Option(def, "suppress_hyperlinks"); - m_optgroup_gui->append_single_option_line(option); - } - def.label = L("Sequential slider applied only to top layer"); def.type = coBool; def.tooltip = L("If enabled, changes made using the sequential slider, in preview, apply only to gcode top layer. " @@ -290,7 +275,31 @@ void PreferencesDialog::build() option = Option(def, "seq_top_layer_only"); m_optgroup_gui->append_single_option_line(option); - m_optgroup_gui->activate(); + if (is_editor) { + def.label = L("Show sidebar collapse/expand button"); + def.type = coBool; + def.tooltip = L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"); + def.set_default_value(new ConfigOptionBool{ app_config->get("show_collapse_button") == "1" }); + option = Option(def, "show_collapse_button"); + m_optgroup_gui->append_single_option_line(option); + + def.label = L("Suppress to open hyperlink in browser"); + def.type = coBool; + def.tooltip = L("If enabled, the descriptions of configuration parameters in settings tabs woldn't work as hyperlinks. " + "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."); + def.set_default_value(new ConfigOptionBool{ app_config->get("suppress_hyperlinks") == "1" }); + option = Option(def, "suppress_hyperlinks"); + m_optgroup_gui->append_single_option_line(option); + + def.label = L("Use custom size for toolbar icons"); + def.type = coBool; + def.tooltip = L("If enabled, you can change size of toolbar icons manually."); + def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" }); + option = Option(def, "use_custom_toolbar_size"); + m_optgroup_gui->append_single_option_line(option); + } + + activate_options_tab(m_optgroup_gui); if (is_editor) { create_icon_size_slider(); @@ -301,8 +310,8 @@ void PreferencesDialog::build() #if ENABLE_ENVIRONMENT_MAP if (is_editor) { - m_optgroup_render = std::make_shared(this, _L("Render")); - m_optgroup_render->label_width = 40; + // Add "Render" tab + m_optgroup_render = create_options_tab(_L("Render"), tabs); m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -314,25 +323,17 @@ void PreferencesDialog::build() option = Option(def, "use_environment_map"); m_optgroup_render->append_single_option_line(option); - m_optgroup_render->activate(); + activate_options_tab(m_optgroup_render); } #endif // ENABLE_ENVIRONMENT_MAP auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(m_optgroup_general->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); - sizer->Add(m_optgroup_camera->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); - sizer->Add(m_optgroup_gui->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); -#if ENABLE_ENVIRONMENT_MAP - if (m_optgroup_render != nullptr) - sizer->Add(m_optgroup_render->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); -#endif // ENABLE_ENVIRONMENT_MAP - - SetFont(wxGetApp().normal_font()); + sizer->Add(tabs, 1, wxEXPAND); auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); - sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 5); + sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM| wxTOP, 10); SetSizer(sizer); sizer->SetSizeHints(this); @@ -410,7 +411,7 @@ void PreferencesDialog::create_icon_size_slider() m_icon_size_sizer = new wxBoxSizer(wxHORIZONTAL); - wxWindow* parent = m_optgroup_gui->ctrl_parent(); + wxWindow* parent = m_optgroup_gui->parent(); if (isOSX) // For correct rendering of the slider and value label under OSX @@ -472,7 +473,7 @@ void PreferencesDialog::create_settings_mode_widget() app_config->get("new_settings_layout_mode") == "1" ? 1 : app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0; - wxWindow* parent = m_optgroup_gui->ctrl_parent(); + wxWindow* parent = m_optgroup_gui->parent(); m_layout_mode_box = new wxRadioBox(parent, wxID_ANY, _L("Layout Options"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 289287c51..400b11859 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -49,9 +49,6 @@ namespace Slic3r { namespace GUI { -wxDEFINE_EVENT(EVT_TAB_VALUE_CHANGED, wxCommandEvent); -wxDEFINE_EVENT(EVT_TAB_PRESETS_CHANGED, SimpleEvent); - void Tab::Highlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) { m_timer.SetOwner(owner, timerid); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index c9914e858..04aa9a0ef 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -98,10 +98,6 @@ protected: }; -wxDECLARE_EVENT(EVT_TAB_VALUE_CHANGED, wxCommandEvent); -wxDECLARE_EVENT(EVT_TAB_PRESETS_CHANGED, SimpleEvent); - - using PageShp = std::shared_ptr; class Tab: public wxPanel { From 6e3fae386533843baadbf03fcf18ec294ebcb968 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Dec 2020 17:45:50 +0100 Subject: [PATCH 060/225] Fix of #5319 - presets inherited from renamed presets using the old preset name in the "inherits" field were losing the "inherits" flag. --- src/libslic3r/Preset.cpp | 6 +++++- src/libslic3r/Preset.hpp | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 3972b20d2..f0bf93a55 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1316,6 +1316,8 @@ std::string PresetCollection::section_name() const } } +// Used for validating the "inherits" flag when importing user's config bundles. +// Returns names of all system presets including the former names of these presets. std::vector PresetCollection::system_preset_names() const { size_t num = 0; @@ -1325,8 +1327,10 @@ std::vector PresetCollection::system_preset_names() const std::vector out; out.reserve(num); for (const Preset &preset : m_presets) - if (preset.is_system) + if (preset.is_system) { out.emplace_back(preset.name); + out.insert(out.end(), preset.renamed_from.begin(), preset.renamed_from.end()); + } std::sort(out.begin(), out.end()); return out; } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index c54dd3fd8..1bf6b94af 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -441,6 +441,8 @@ public: { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); } // Return a sorted list of system preset names. + // Used for validating the "inherits" flag when importing user's config bundles. + // Returns names of all system presets including the former names of these presets. std::vector system_preset_names() const; // Update a dirty flag of the current preset From beabe2fe7e0bc77a05e3c10062358b78f9e593c9 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Tue, 8 Dec 2020 22:12:15 +0100 Subject: [PATCH 061/225] Anycubic Kossel. Updated end g-code, before layer change g-code and output filename format. --- resources/profiles/Anycubic.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index 74457052a..c36f50ec8 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -112,7 +112,7 @@ notes = overhangs = 0 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{print_preset}_{filament_type[0]}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 @@ -376,7 +376,7 @@ filament_vendor = Generic [printer:*common_akossel*] printer_technology = FFF bed_shape = -before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0\n;[layer_z] between_objects_gcode = deretract_speed = 40 extruder_colour = #FFFF00 @@ -420,7 +420,7 @@ retract_restart_extra_toolchange = 0 retract_speed = 60 single_extruder_multi_material = 0 start_gcode = -end_gcode = M104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+10, max_print_height)} F600{endif} ; Move print head up\nG1 X0 Y100 F3000 ; present print\nM84 ; disable motors +end_gcode = M104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG28 ; home\nM84 ; disable motors toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 From fe4f1b15c64f5b299e866d27829583357f4cf155 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Tue, 8 Dec 2020 22:16:13 +0100 Subject: [PATCH 062/225] Anycubic 0.0.6 --- resources/profiles/Anycubic.idx | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/profiles/Anycubic.idx b/resources/profiles/Anycubic.idx index 929e600be..062a0ec00 100644 --- a/resources/profiles/Anycubic.idx +++ b/resources/profiles/Anycubic.idx @@ -1,4 +1,5 @@ min_slic3r_version = 2.3.0-beta2 +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 filaname format, infill overlap. From 2d6b6942614e387b671edb22e716d514e55a6a1d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Dec 2020 07:59:25 +0100 Subject: [PATCH 063/225] Fix of Place on face: 1) Decreased "gimbal lock" epsilon 10x 2) Got rid of unnecessary back and forth conversions which led to numerical inacurracies This should fix issues #5172, #5011 and #5398 --- src/libslic3r/Geometry.cpp | 2 +- src/slic3r/GUI/Selection.cpp | 43 +++++++----------------------------- 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index b263aecfd..45730dd9f 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1223,7 +1223,7 @@ Vec3d extract_euler_angles(const Eigen::Matrix& // reference: http://www.gregslabaugh.net/publications/euler.pdf Vec3d angles1 = Vec3d::Zero(); Vec3d angles2 = Vec3d::Zero(); - if (is_approx(std::abs(rotation_matrix(2, 0)), 1.0)) + if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5) { angles1(2) = 0.0; if (rotation_matrix(2, 0) < 0.0) // == -1.0 diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 610881aed..cdd3ebe85 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -841,41 +841,14 @@ void Selection::flattening_rotate(const Vec3d& normal) for (unsigned int i : m_list) { - Transform3d wst = m_cache.volumes_data[i].get_instance_scale_matrix(); - Vec3d scaling_factor = Vec3d(1. / wst(0, 0), 1. / wst(1, 1), 1. / wst(2, 2)); - - Transform3d wmt = m_cache.volumes_data[i].get_instance_mirror_matrix(); - Vec3d mirror(wmt(0, 0), wmt(1, 1), wmt(2, 2)); - - Vec3d rotation = Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix()); - Vec3d tnormal = Geometry::assemble_transform(Vec3d::Zero(), rotation, scaling_factor, mirror) * normal; - tnormal.normalize(); - - // Calculate rotation axis. It shall be perpendicular to "down" direction - // and the normal, so the rotation is the shortest possible and logical. - Vec3d axis = tnormal.cross(-Vec3d::UnitZ()); - - // Make sure the axis is not zero and normalize it. "Almost" zero is not interesting. - // In case the vectors are almost colinear, the rotation axis does not matter much. - if (axis == Vec3d::Zero()) - axis = Vec3d::UnitX(); - axis.normalize(); - - // Calculate the angle using the component where we achieve more precision. - // Cosine of small angles is const in first order. No good. - double angle = 0.; - if (std::abs(tnormal.z()) < std::sqrt(2.)/2.) - angle = std::acos(-tnormal.z()); - else { - double xy = std::hypot(tnormal.x(), tnormal.y()); - angle = PI/2. + std::acos(xy * (tnormal.z() > 0.)); - } - - Transform3d extra_rotation = Transform3d::Identity(); - extra_rotation.rotate(Eigen::AngleAxisd(angle, axis)); - - Vec3d new_rotation = Geometry::extract_euler_angles(extra_rotation * m_cache.volumes_data[i].get_instance_rotation_matrix()); - (*m_volumes)[i]->set_instance_rotation(new_rotation); + // Normal transformed from the object coordinate space to the world coordinate space. + const auto &voldata = m_cache.volumes_data[i]; + Vec3d tnormal = (Geometry::assemble_transform( + Vec3d::Zero(), voldata.get_instance_rotation(), + voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); + // Additional rotation to align tnormal with the down vector in the world coordinate space. + auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, - Vec3d::UnitZ()); + (*m_volumes)[i]->set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); } #if !DISABLE_INSTANCES_SYNCH From 4c3525053134ceea7a458d2aa459dbbe0a3149c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 9 Dec 2020 07:16:01 +0100 Subject: [PATCH 064/225] Fix of #5004 - Disable retract on layer change also for filament overrides. --- src/libslic3r/PrintConfig.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fcdb3ab04..49e1d416e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3356,8 +3356,11 @@ void DynamicPrintConfig::normalize_fdm() if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { { // this should be actually done only on the spiral layers instead of all - ConfigOptionBools* opt = this->opt("retract_layer_change", true); + auto* opt = this->opt("retract_layer_change", true); opt->values.assign(opt->values.size(), false); // set all values to false + // Disable retract on layer change also for filament overrides. + auto* opt_n = this->opt("filament_retract_layer_change", true); + opt_n->values.assign(opt_n->values.size(), false); // Set all values to false. } { this->opt("perimeters", true)->value = 1; From 0ad135ff9314ba92a329d3f0df6117ecb71a58c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 9 Dec 2020 07:21:14 +0100 Subject: [PATCH 065/225] Fix of #4732 - In spiral vase mode, use the number of layers instead of the value of bottom solid layers if a printed object has a smaller number of layers than the value of bottom solid layers. --- src/libslic3r/PrintObject.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 51b3128ba..f344d79b9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -719,7 +719,7 @@ void PrintObject::detect_surfaces_type() // should be visible. bool spiral_vase = this->print()->config().spiral_vase.value; bool interface_shells = ! spiral_vase && m_config.interface_shells.value; - size_t num_layers = spiral_vase ? first_printing_region(*this)->config().bottom_solid_layers : m_layers.size(); + size_t num_layers = spiral_vase ? std::min(size_t(first_printing_region(*this)->config().bottom_solid_layers), m_layers.size()) : m_layers.size(); for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start"; @@ -1007,7 +1007,7 @@ void PrintObject::discover_vertical_shells() Polygons holes; }; bool spiral_vase = this->print()->config().spiral_vase.value; - size_t num_layers = spiral_vase ? first_printing_region(*this)->config().bottom_solid_layers : m_layers.size(); + size_t num_layers = spiral_vase ? std::min(size_t(first_printing_region(*this)->config().bottom_solid_layers), m_layers.size()) : m_layers.size(); coordf_t min_layer_height = this->slicing_parameters().min_layer_height; // Does this region possibly produce more than 1 top or bottom layer? auto has_extra_layers_fn = [min_layer_height](const PrintRegionConfig &config) { From bb2eecb42c4cdae4e72d6dd1c6a7ee5546f648bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 9 Dec 2020 07:28:18 +0100 Subject: [PATCH 066/225] Fix of #4060 - Add check for the case that all ranges of a volume do not contain any Z. --- src/libslic3r/PrintObject.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f344d79b9..a409f12e1 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2014,7 +2014,7 @@ std::vector PrintObject::slice_region(size_t region_id, const std::v return this->slice_volumes(z, mode, volumes); } -// Z ranges are not applicable to modifier meshes, therefore a sinle volume will be found in volume_and_range at most once. +// Z ranges are not applicable to modifier meshes, therefore a single volume will be found in volume_and_range at most once. std::vector PrintObject::slice_modifiers(size_t region_id, const std::vector &slice_zs) const { std::vector out; @@ -2080,10 +2080,12 @@ std::vector PrintObject::slice_modifiers(size_t region_id, const std ranges.emplace_back(volumes_and_ranges[j].first); // slicing in parallel std::vector this_slices = this->slice_volume(slice_zs, ranges, SlicingMode::Regular, *model_volume); + // Variable this_slices could be empty if no value of slice_zs is within any of the ranges of this volume. if (out.empty()) { out = std::move(this_slices); merge.assign(out.size(), false); - } else { + } else if (!this_slices.empty()) { + assert(out.size() == this_slices.size()); for (size_t i = 0; i < out.size(); ++ i) if (! this_slices[i].empty()) { if (! out[i].empty()) { @@ -2188,7 +2190,7 @@ std::vector PrintObject::slice_volume(const std::vector &z, S return layers; } -// Filter the zs not inside the ranges. The ranges are closed at the botton and open at the top, they are sorted lexicographically and non overlapping. +// Filter the zs not inside the ranges. The ranges are closed at the bottom and open at the top, they are sorted lexicographically and non overlapping. std::vector PrintObject::slice_volume(const std::vector &z, const std::vector &ranges, SlicingMode mode, const ModelVolume &volume) const { std::vector out; From f0e02ea5d0e3eef49aa8792ccbd5901c5c183b09 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 9 Dec 2020 08:51:22 +0100 Subject: [PATCH 067/225] Localization: Updated dictionaries CZ, DE, ES, FR, IT, NL, PL --- resources/localization/cs/PrusaSlicer.mo | Bin 280144 -> 293102 bytes resources/localization/cs/PrusaSlicer_cs.po | 1146 ++--- resources/localization/de/PrusaSlicer.mo | Bin 288040 -> 302172 bytes resources/localization/de/PrusaSlicer_de.po | 1233 ++--- resources/localization/es/PrusaSlicer.mo | Bin 287640 -> 301220 bytes resources/localization/es/PrusaSlicer_es.po | 1225 ++--- resources/localization/fr/PrusaSlicer.mo | Bin 295290 -> 310302 bytes resources/localization/fr/PrusaSlicer_fr.po | 1212 ++--- resources/localization/it/PrusaSlicer.mo | Bin 282315 -> 296687 bytes resources/localization/it/PrusaSlicer_it.po | 1183 ++--- resources/localization/nl/PrusaSlicer.mo | Bin 240125 -> 280804 bytes resources/localization/nl/PrusaSlicer_nl.po | 4524 ++++++++++--------- resources/localization/pl/PrusaSlicer.mo | Bin 280143 -> 293544 bytes resources/localization/pl/PrusaSlicer_pl.po | 1231 ++--- resources/localization/pt_br/PrusaSlicer.mo | Bin 281511 -> 281554 bytes 15 files changed, 6177 insertions(+), 5577 deletions(-) diff --git a/resources/localization/cs/PrusaSlicer.mo b/resources/localization/cs/PrusaSlicer.mo index b1a000e92e581a87d964a1f11206d2ac12edfd98..444aef04f2ac22c0d7e9075008b00104c8064742 100644 GIT binary patch delta 63557 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%0DG?AHtI#}*t5loiK=Ot}T>uOZe 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 T`=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Poedit 2.4.2\n" -"Project-Id-Version: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language-Team: \n" +"X-Generator: PhraseApp (phraseapp.com)\n" #: src/slic3r/GUI/Tab.cpp:4124 -msgid "" -"\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" -"To enable \"%1%\", please switch off \"%2%\"" -msgstr "" -"\"%1%\" je deaktivováno, protože \"%2%\" je zapnuto v kategorii \"%3%\".\n" -"Chcete-li povolit \"%1%\",, vypněte \"%2%\"" +msgid "\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\nTo enable \"%1%\", please switch off \"%2%\"" +msgstr "\"%1%\" je deaktivováno, protože \"%2%\" je zapnuto v kategorii \"%3%\".\nChcete-li povolit \"%1%\",, vypněte \"%2%\"" #: src/libslic3r/PrintConfig.cpp:249 src/libslic3r/PrintConfig.cpp:828 #: src/libslic3r/PrintConfig.cpp:1148 src/libslic3r/PrintConfig.cpp:1327 @@ -29,7 +20,7 @@ msgid "%" msgstr "%" #: src/slic3r/GUI/GLCanvas3D.cpp:969 -#, c-format +#, possible-c-format msgid "%.2f - %.2f mm" msgstr "%.2f - %.2f mm" @@ -51,145 +42,113 @@ msgid "%1%=%2% mm is too low to be printable at a layer height %3% mm" msgstr "%1%=%2% mm je příliš nízké na to, aby bylo možné tisknout ve výšce vrstvy %3% mm" #: src/slic3r/GUI/PresetHints.cpp:228 -#, c-format +#, possible-c-format msgid "%3.2f mm³/s at filament speed %3.2f mm/s." msgstr "%3.2f mm³/s při rychlosti filamentu %3.2f mm/s." #: src/slic3r/GUI/Plater.cpp:1061 -#, c-format +#, possible-c-format msgid "%d (%d shells)" msgstr "%d (%d obalů)" #: src/slic3r/GUI/Plater.cpp:1069 -#, c-format +#, possible-c-format msgid "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges" msgstr "%d poškozených faset, %d okrajů opraveno, %d faset odstraněno, %d faset přidáno, %d faset navráceno, %d zadních okrajů" #: src/slic3r/GUI/PresetHints.cpp:269 -#, c-format +#, possible-c-format msgid "%d lines: %.2f mm" msgstr "%d perimetry: %.2f mm" #: src/slic3r/GUI/MainFrame.cpp:1728 -#, c-format +#, possible-c-format msgid "%d presets successfully imported." msgstr "%d přednastavení úspěšně importováno." #: src/slic3r/GUI/GUI_App.cpp:718 -#, c-format -msgid "" -"%s\n" -"Do you want to continue?" -msgstr "" -"%s\n" -"Chcete pokračovat?" +#, possible-c-format +msgid "%s\nDo you want to continue?" +msgstr "%s\nChcete pokračovat?" #: src/slic3r/GUI/MainFrame.cpp:917 src/slic3r/GUI/MainFrame.cpp:1316 -#, c-format +#, possible-c-format msgid "%s &Website" msgstr "%s &Webová stránka" #: src/slic3r/GUI/GUI_App.cpp:394 -#, c-format +#, possible-c-format msgid "%s - BREAKING CHANGE" msgstr "%s - ZLOMOVÁ ZMĚNA" #: src/slic3r/GUI/Plater.cpp:1410 -#, c-format +#, possible-c-format msgid "%s - Drop project file" msgstr "%s - Otevírání projektu" #: src/slic3r/GUI/UpdateDialogs.cpp:211 -#, c-format +#, possible-c-format msgid "%s configuration is incompatible" msgstr "Konfigurace %s není kompatibilní" #: src/slic3r/GUI/Field.cpp:223 -#, c-format +#, possible-c-format msgid "%s doesn't support percentage" msgstr "%s nepodporuje procenta" #: src/slic3r/GUI/MsgDialog.cpp:73 -#, c-format +#, possible-c-format msgid "%s error" msgstr "%s chyba" #: src/slic3r/GUI/ConfigWizard.cpp:499 -#, c-format +#, possible-c-format msgid "%s Family" msgstr "%s Rodina" #: src/slic3r/GUI/MsgDialog.cpp:74 -#, c-format +#, possible-c-format msgid "%s has encountered an error" msgstr "Došlo k chybě v programu %s" #: src/slic3r/GUI/GUI_App.cpp:528 -#, c-format -msgid "" -"%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it.\n" -"\n" -"The application will now terminate." -msgstr "" -"%s zaznamenal chybu. Bylo to pravděpodobně způsobeno nedostatkem paměti. Pokud jste si jisti, že máte v systému dostatek paměti RAM, může to být také chyba programu a v takovém případě bychom byli rádi, kdybyste nám to nahlásili.\n" -"\n" -"Aplikace se nyní ukončí." +#, possible-c-format +msgid "%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it.\n\nThe application will now terminate." +msgstr "%s zaznamenal chybu. Bylo to pravděpodobně způsobeno nedostatkem paměti. Pokud jste si jisti, že máte v systému dostatek paměti RAM, může to být také chyba programu a v takovém případě bychom byli rádi, kdybyste nám to nahlásili.\n\nAplikace se nyní ukončí." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:62 -#, c-format +#, possible-c-format msgid "%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it." msgstr "%s zaznamenal chybu. Bylo to pravděpodobně způsobeno nedostatkem paměti. Pokud jste si jisti, že máte v systému dostatek paměti RAM, může to být také chyba programu a v takovém případě bychom byli rádi, kdybyste nám to nahlásili." #: src/slic3r/GUI/UpdateDialogs.cpp:309 -#, c-format +#, possible-c-format msgid "%s has no configuration updates available." msgstr "%s nemá k dispozici žádné aktualizace konfigurace." #: src/slic3r/GUI/UpdateDialogs.cpp:148 src/slic3r/GUI/UpdateDialogs.cpp:210 -#, c-format +#, possible-c-format msgid "%s incompatibility" msgstr "Není kompatibilní s %s" #: src/slic3r/GUI/UpdateDialogs.cpp:270 -#, c-format -msgid "" -"%s now uses an updated configuration structure.\n" -"\n" -"So called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\n" -"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n" -"\n" -"Please proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." -msgstr "" -"%s nyní používá aktualizovanou konfigurační strukturu.\n" -"\n" -"Byly uvedeny takzvaná \"Systémová přednastavení\", která obsahují výchozí nastavení pro rozličné tiskárny. Tato systémová přednastavení nemohou být upravena, místo toho si nyní uživatel může vytvořit svá vlastní přednastavení tím, že zdědí nastavení z jednoho ze systémových přednastavení.\n" -"Nově vytvořené přednastavení může buď zdědit určitou hodnotu od svého předchůdce nebo ji přepsat upravenou hodnotou.\n" -"\n" -"Při nastavování nových předvoleb postupujte podle pokynů v %s a vyberte, zda chcete povolit automatické přednastavené aktualizace." +#, possible-c-format +msgid "%s now uses an updated configuration structure.\n\nSo called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\nAn inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\nPlease proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." +msgstr "%s nyní používá aktualizovanou konfigurační strukturu.\n\nByly uvedeny takzvaná \"Systémová přednastavení\", která obsahují výchozí nastavení pro rozličné tiskárny. Tato systémová přednastavení nemohou být upravena, místo toho si nyní uživatel může vytvořit svá vlastní přednastavení tím, že zdědí nastavení z jednoho ze systémových přednastavení.\nNově vytvořené přednastavení může buď zdědit určitou hodnotu od svého předchůdce nebo ji přepsat upravenou hodnotou.\n\nPři nastavování nových předvoleb postupujte podle pokynů v %s a vyberte, zda chcete povolit automatické přednastavené aktualizace." #: src/slic3r/GUI/GUI_App.cpp:1512 -#, c-format +#, possible-c-format msgid "%s View Mode" msgstr "%s Režim zobrazení" #: src/slic3r/GUI/UpdateDialogs.cpp:151 -#, c-format -msgid "" -"%s will now start updates. Otherwise it won't be able to start.\n" -"\n" -"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" -"\n" -"Updated configuration bundles:" -msgstr "" -"%s nyní spustí aktualizaci. Jinak nebude moci být spuštěn.\n" -"\n" -"Nejprve bude vytvořen kompletní snímek konfigurace a v případě problému s novou verzí lze provést obnovu.\n" -"\n" -"Aktualizované balíčky konfigurace:" +#, possible-c-format +msgid "%s will now start updates. Otherwise it won't be able to start.\n\nNote that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n\nUpdated configuration bundles:" +msgstr "%s nyní spustí aktualizaci. Jinak nebude moci být spuštěn.\n\nNejprve bude vytvořen kompletní snímek konfigurace a v případě problému s novou verzí lze provést obnovu.\n\nAktualizované balíčky konfigurace:" #: src/slic3r/GUI/MainFrame.cpp:933 src/slic3r/GUI/MainFrame.cpp:937 #: src/slic3r/GUI/MainFrame.cpp:1329 -#, c-format +#, possible-c-format msgid "&About %s" msgstr "&O %su" @@ -317,6 +276,10 @@ msgstr "&Okno" msgid "(All)" msgstr "(Všechny)" +#: src/slic3r/GUI/Plater.cpp:1195 +msgid "(including spool)" +msgstr "(včetně cívky)" + #: src/libslic3r/PrintConfig.cpp:1554 msgid "(minimum)" msgstr "(minimálně)" @@ -333,10 +296,22 @@ msgstr "&(Znovu) Slicovat" msgid "(Unknown)" msgstr "(Neznámý)" +#: src/slic3r/GUI/Plater.cpp:1195 +msgid "(weight with spool)" +msgstr "(včetně cívky)" + #: src/slic3r/GUI/MainFrame.cpp:1491 msgid ") not found." msgstr ") nebyl nalezen." +#: src/libslic3r/PrintConfig.cpp:1085 +msgid "0 (no open anchors)" +msgstr "0 (žádné otevřené kotvy)" + +#: src/libslic3r/PrintConfig.cpp:1107 +msgid "0 (not anchored)" +msgstr "0 (není ukotven)" + #: src/libslic3r/PrintConfig.cpp:2060 msgid "0 (soluble)" msgstr "0 (rozpustné)" @@ -345,6 +320,10 @@ msgstr "0 (rozpustné)" msgid "0.2 (detachable)" msgstr "0.2 (oddělitelné)" +#: src/libslic3r/PrintConfig.cpp:1090 src/libslic3r/PrintConfig.cpp:1112 +msgid "1000 (unlimited)" +msgstr "1 000 (neomezeně)" + #: src/slic3r/GUI/MainFrame.cpp:1234 msgid "3&D" msgstr "3&D" @@ -366,7 +345,7 @@ msgid "3Dconnexion settings" msgstr "Nastavení 3DConnexion" #: src/slic3r/GUI/Plater.cpp:5167 -#, c-format +#, possible-c-format msgid "3MF file exported to %s" msgstr "Soubor 3MF byl exportován do %s" @@ -399,7 +378,7 @@ msgid "A toolpath outside the print area was detected." msgstr "Byla detekována cesta mimo tiskovou oblast." #: src/slic3r/GUI/AboutDialog.cpp:212 src/slic3r/GUI/AboutDialog.cpp:215 -#, c-format +#, possible-c-format msgid "About %s" msgstr "O %s" @@ -408,7 +387,7 @@ msgid "above" msgstr "nad" #: src/slic3r/GUI/GLCanvas3D.cpp:965 -#, c-format +#, possible-c-format msgid "above %.2f mm" msgstr "nad %.2f mm" @@ -549,7 +528,7 @@ msgid "Add modifier" msgstr "Přidat modifikátor" #: src/libslic3r/PrintConfig.cpp:515 -#, c-format +#, possible-c-format msgid "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r keeps adding perimeters, until more than 70% of the loop immediately above is supported." msgstr "Přidání více perimetrů, pokud je potřeba, pro vyvarování se tvorbě mezer v šikmých plochách. Slic3r pokračuje v přidávání perimetrů, dokud není podepřeno více než 70% perimetrů v následující vrstvě." @@ -720,6 +699,10 @@ msgstr "Zarovnat XY" msgid "Aligned" msgstr "Zarovnaný" +#: src/libslic3r/PrintConfig.cpp:470 src/libslic3r/PrintConfig.cpp:902 +msgid "Aligned Rectilinear" +msgstr "Zarovnaná přímočará" + #: src/slic3r/GUI/ConfigWizard.cpp:308 src/slic3r/GUI/ConfigWizard.cpp:598 #: src/slic3r/GUI/Tab.cpp:3507 src/slic3r/GUI/UnsavedChangesDialog.cpp:921 msgid "All" @@ -745,10 +728,18 @@ msgstr "Všechny objekty budou odebrány, pokračovat?" msgid "All settings changes will be discarded." msgstr "Všechny změny v nastavení budou zahozeny." +#: src/libslic3r/PrintConfig.cpp:1212 +msgid "All solid surfaces" +msgstr "Všechny plné povrchy" + #: src/slic3r/GUI/ConfigWizard.cpp:307 msgid "All standard" msgstr "Všechny běžné" +#: src/libslic3r/PrintConfig.cpp:1210 +msgid "All top surfaces" +msgstr "Všechny horní povrchy" + #: src/libslic3r/miniz_extension.cpp:121 msgid "allocation failed" msgstr "alokace selhala" @@ -779,17 +770,13 @@ msgid "Always ask for unsaved changes when selecting new preset" msgstr "Při výběru nového přednastavení se vždy dotázat na neuložené změny" #: src/slic3r/GUI/Plater.cpp:5135 -#, c-format +#, possible-c-format msgid "AMF file exported to %s" msgstr "Soubor AMF byl exportován do %s" #: src/slic3r/GUI/GLCanvas3D.cpp:638 -msgid "" -"An object outside the print area was detected.\n" -"Resolve the current problem to continue slicing." -msgstr "" -"Byl detekován objekt mimo tiskovou oblast.\n" -"Pro pokračování ve slicování vyřešte tento problém." +msgid "An object outside the print area was detected.\nResolve the current problem to continue slicing." +msgstr "Byl detekován objekt mimo tiskovou oblast.\nPro pokračování ve slicování vyřešte tento problém." #: src/slic3r/GUI/GLCanvas3D.cpp:633 msgid "An object outside the print area was detected." @@ -808,6 +795,10 @@ msgstr "Šipky" msgid "Any modifications should be saved as a new preset inherited from this one." msgstr "Jakékoliv úpravy by měly být uloženy jako nové přednastavení zděděná z tohoto." +#: src/libslic3r/PrintConfig.cpp:162 +msgid "API key" +msgstr "API klíč" + #: src/libslic3r/PrintConfig.cpp:106 msgid "API Key / Password" msgstr "API klíč / Heslo" @@ -838,12 +829,8 @@ msgid "Are you sure you want to %1% the selected preset?" msgstr "Opravdu chcete %1% vybrané přednastavení?" #: src/slic3r/GUI/FirmwareDialog.cpp:902 -msgid "" -"Are you sure you want to cancel firmware flashing?\n" -"This could leave your printer in an unusable state!" -msgstr "" -"Opravdu chcete ukončit nahrávání firmware?\n" -"Tiskárna může zůstat v nefunkčním stavu!" +msgid "Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!" +msgstr "Opravdu chcete ukončit nahrávání firmware?\nTiskárna může zůstat v nefunkčním stavu!" #: src/slic3r/GUI/DoubleSlider.cpp:2122 src/slic3r/GUI/DoubleSlider.cpp:2142 msgid "Are you sure you want to continue?" @@ -874,6 +861,10 @@ msgstr "Okolo objektu" msgid "Arrange" msgstr "Uspořádat" +#: src/slic3r/GUI/GLCanvas3D.cpp:3889 +msgid "Arrange options" +msgstr "Volby uspořádání" + #: src/slic3r/GUI/GLCanvas3D.cpp:4859 src/slic3r/GUI/KBShortcutsDialog.cpp:152 msgid "Arrange selection" msgstr "Uspořádat výběr" @@ -936,6 +927,18 @@ msgstr "Zeptat se na neuložené změny při zavírání aplikace" msgid "Ask for unsaved changes when selecting new preset" msgstr "Zeptat se na neuložené změny při výběru nového profilu" +#: src/slic3r/GUI/ConfigWizard.cpp:1183 src/slic3r/GUI/Preferences.cpp:91 +msgid "Associate .3mf files to PrusaSlicer" +msgstr "Otevírat soubory .3mf v PrusaSliceru" + +#: src/slic3r/GUI/Preferences.cpp:177 +msgid "Associate .gcode files to PrusaSlicer G-code Viewer" +msgstr "Otevírat soubory .gcode v prohlížeči PrusaSlicer G-code Viewer" + +#: src/slic3r/GUI/ConfigWizard.cpp:1184 src/slic3r/GUI/Preferences.cpp:98 +msgid "Associate .stl files to PrusaSlicer" +msgstr "Otevírat soubory .stl v PrusaSliceru" + #: src/slic3r/GUI/GUI_App.cpp:1878 src/slic3r/GUI/Jobs/SLAImportJob.cpp:210 #: src/slic3r/GUI/Plater.cpp:2256 src/slic3r/GUI/Tab.cpp:3189 msgid "Attention!" @@ -959,12 +962,12 @@ msgid "Auto-generate points" msgstr "Automatické generování bodů" #: src/slic3r/GUI/Plater.cpp:1066 -#, c-format +#, possible-c-format msgid "Auto-repaired (%d errors)" msgstr "Automaticky opraveno (%d chyb)" #: src/slic3r/GUI/GUI_ObjectList.cpp:386 -#, c-format +#, possible-c-format msgid "Auto-repaired (%d errors):" msgstr "Automaticky opraveno ( %d chyb):" @@ -1013,20 +1016,12 @@ msgid "BACK ARROW" msgstr "ŠIPKA ZPĚT" #: src/slic3r/GUI/Tab.cpp:3727 -msgid "" -"BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" -"Click to reset all settings for the current option group to the last saved preset." -msgstr "" -"Ikona ŠIPKY ZPĚT indikuje, že došlo ke změně nastavení, které není shodné s naposledy uloženým přednastavením pro aktuální skupinu nastavení.\n" -"Klikněte pro reset všech nastavení pro aktuální skupinu nastavení na naposledy uložené přednastavení." +msgid "BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\nClick to reset all settings for the current option group to the last saved preset." +msgstr "Ikona ŠIPKY ZPĚT indikuje, že došlo ke změně nastavení, které není shodné s naposledy uloženým přednastavením pro aktuální skupinu nastavení.\nKlikněte pro reset všech nastavení pro aktuální skupinu nastavení na naposledy uložené přednastavení." #: src/slic3r/GUI/Tab.cpp:3741 -msgid "" -"BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" -"Click to reset current value to the last saved preset." -msgstr "" -"Ikona ŠIPKY ZPĚT indikuje, že se hodnota změnila a není shodná s naposledy uloženým přednastavením.\n" -"Klikněte pro reset současné hodnoty na naposledy uložené přednastavení." +msgid "BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\nClick to reset current value to the last saved preset." +msgstr "Ikona ŠIPKY ZPĚT indikuje, že se hodnota změnila a není shodná s naposledy uloženým přednastavením.\nKlikněte pro reset současné hodnoty na naposledy uložené přednastavení." #: src/slic3r/GUI/Preferences.cpp:72 msgid "Background processing" @@ -1226,24 +1221,16 @@ msgid "buffer too small" msgstr "buffer je příliš malý" #: src/slic3r/GUI/GUI_App.cpp:1152 -msgid "" -"But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" -"Settings will be available in physical printers settings." -msgstr "" -"Ale od této verze PrusaSliceru již nebudeme tyto informace zobrazovat v Nastavení tiskárny.\n" -"Nastavení bude k dispozici v nastavení fyzických tiskáren." +msgid "But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\nSettings will be available in physical printers settings." +msgstr "Ale od této verze PrusaSliceru již nebudeme tyto informace zobrazovat v Nastavení tiskárny.\nNastavení bude k dispozici v nastavení fyzických tiskáren." #: src/slic3r/GUI/ButtonsDescription.cpp:16 msgid "Buttons And Text Colors Description" msgstr "Barvy pro textové popisky a tlačítka" #: src/slic3r/GUI/GUI_App.cpp:1084 -msgid "" -"By default new Printer devices will be named as \"Printer N\" during its creation.\n" -"Note: This name can be changed later from the physical printers settings" -msgstr "" -"Ve výchozím stavu budou při vytváření nové tiskárny pojmenovány jako „Printer N“.\n" -"Poznámka: Tento název lze později změnit v nastavení fyzických tiskáren" +msgid "By default new Printer devices will be named as \"Printer N\" during its creation.\nNote: This name can be changed later from the physical printers settings" +msgstr "Ve výchozím stavu budou při vytváření nové tiskárny pojmenovány jako „Printer N“.\nPoznámka: Tento název lze později změnit v nastavení fyzických tiskáren" #: src/slic3r/GUI/PresetHints.cpp:222 msgid "by the print profile maximum" @@ -1285,32 +1272,16 @@ msgid "Cannot calculate extrusion width for %1%: Variable \"%2%\" not accessible msgstr "Nelze vypočítat šířku extrudování pro %1%: Proměnná \"%2%\" není dostupná." #: src/slic3r/GUI/GUI_ObjectList.cpp:3400 -msgid "" -"Cannot insert a new layer range after the current layer range.\n" -"Current layer range overlaps with the next layer range." -msgstr "" -"Nelze vložit nový rozsah vrstev za aktuální rozsah vrstev.\n" -"Aktuální rozsah vrstev se překrývá s dalším rozsahem vrstev." +msgid "Cannot insert a new layer range after the current layer range.\nCurrent layer range overlaps with the next layer range." +msgstr "Nelze vložit nový rozsah vrstev za aktuální rozsah vrstev.\nAktuální rozsah vrstev se překrývá s dalším rozsahem vrstev." #: src/slic3r/GUI/GUI_ObjectList.cpp:3391 -msgid "" -"Cannot insert a new layer range after the current layer range.\n" -"The next layer range is too thin to be split to two\n" -"without violating the minimum layer height." -msgstr "" -"Nelze vložit nový rozsah vrstev za aktuální rozsah vrstev.\n" -"Další rozsah vrstev je příliš tenký na to, aby byl rozdělen na dva\n" -"bez porušení minimální výšky vrstvy." +msgid "Cannot insert a new layer range after the current layer range.\nThe next layer range is too thin to be split to two\nwithout violating the minimum layer height." +msgstr "Nelze vložit nový rozsah vrstev za aktuální rozsah vrstev.\nDalší rozsah vrstev je příliš tenký na to, aby byl rozdělen na dva\nbez porušení minimální výšky vrstvy." #: src/slic3r/GUI/GUI_ObjectList.cpp:3395 -msgid "" -"Cannot insert a new layer range between the current and the next layer range.\n" -"The gap between the current layer range and the next layer range\n" -"is thinner than the minimum layer height allowed." -msgstr "" -"Nelze vložit nový rozsah vrstev mezi aktuální a následující rozsah vrstev.\n" -"Mezera mezi aktuálním rozsahem vrstev a dalším rozsahem vrstev\n" -"je tenčí, než je minimální povolená výška vrstvy." +msgid "Cannot insert a new layer range between the current and the next layer range.\nThe gap between the current layer range and the next layer range\nis thinner than the minimum layer height allowed." +msgstr "Nelze vložit nový rozsah vrstev mezi aktuální a následující rozsah vrstev.\nMezera mezi aktuálním rozsahem vrstev a dalším rozsahem vrstev\nje tenčí, než je minimální povolená výška vrstvy." #: src/slic3r/GUI/SavePresetDialog.cpp:137 msgid "Cannot overwrite a system profile." @@ -1377,7 +1348,7 @@ msgid "Change Extruders" msgstr "Změnit Extrudery" #: src/slic3r/GUI/GUI_ObjectSettings.cpp:157 -#, c-format +#, possible-c-format msgid "Change Option %s" msgstr "Změna parametru %s" @@ -1462,10 +1433,18 @@ msgstr "Kruh" msgid "Circular" msgstr "Kruhový" +#: src/slic3r/GUI/GLCanvas3D.cpp:3961 +msgid "Clearance size" +msgstr "Velikost mezery" + #: src/slic3r/GUI/GLCanvas3D.cpp:5028 src/slic3r/GUI/GLCanvas3D.cpp:5067 msgid "Click right mouse button to open/close History" msgstr "Stisk pravého tlačítka myši pro zobrazení/skrytí Historie" +#: src/slic3r/GUI/GLCanvas3D.cpp:4341 +msgid "Click right mouse button to show arrangement options" +msgstr "Kliknutím pravým tlačítkem myši zobrazíte možnosti uspořádání" + #: src/slic3r/GUI/GUI_ObjectList.cpp:451 msgid "Click the icon to change the object printable property" msgstr "Klepnutím na ikonu změníte příznak objektu, zda se bude tisknout či nikoliv" @@ -1533,7 +1512,7 @@ msgid "Color change (\"%1%\") for Extruder %2%" msgstr "Změna barvy (\"%1%\") pro Extruder %2%" #: src/slic3r/GUI/GLCanvas3D.cpp:1001 -#, c-format +#, possible-c-format msgid "Color change for Extruder %d at %.2f mm" msgstr "Změna barvy pro extruder %d ve výšce %.2f mm" @@ -1650,6 +1629,14 @@ msgstr "Průvodce nastavením" msgid "Confirmation" msgstr "Potvrzení" +#: src/libslic3r/PrintConfig.cpp:1070 +msgid "Connect an infill line to an internal perimeter with a short segment of an additional perimeter. If expressed as percentage (example: 15%) it is calculated over infill extrusion width. PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment shorter than infill_anchor_max is found, the infill line is connected to a perimeter segment at just one side and the length of the perimeter segment taken is limited to this parameter, but no longer than anchor_length_max. Set this parameter to zero to disable anchoring perimeters connected to a single infill line." +msgstr "Připojení výplně k vnitřnímu perimetru krátkým segmentem dalšího perimetru. Pokud je vyjádřeno v procentech (příklad: 15%), vypočítává se z šířky extruze infilu. PrusaSlicer se pokouší spojit dvě blízké výplňová čáry krátkým obvodovým perimetrem. Pokud není nalezen žádný takový obvodový perimetr kratší než infill_anchor_max, je výplňová čára spojena s obvodovým perimetrem pouze na jedné straně a délka odebraného obvodového perimetru je omezena na tento parametr, ale ne dále než anchor_length_max. Nastavením tohoto parametru na nulu deaktivujete kotvící perimetry připojené k jedné výplňové čáře." + +#: src/libslic3r/PrintConfig.cpp:1097 +msgid "Connect an infill line to an internal perimeter with a short segment of an additional perimeter. If expressed as percentage (example: 15%) it is calculated over infill extrusion width. PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment shorter than this parameter is found, the infill line is connected to a perimeter segment at just one side and the length of the perimeter segment taken is limited to infill_anchor, but no longer than this parameter. Set this parameter to zero to disable anchoring." +msgstr "Připojení výplně k vnitřnímu perimetru krátkým segmentem dalšího perimetru. Pokud je vyjádřeno v procentech (příklad: 15%), vypočítává se z šířky extruze infilu. PrusaSlicer se pokouší spojit dvě blízké výplňová čáry krátkým obvodovým perimetrem. Pokud není nalezen žádný takový obvodový perimetr kratší než tento parametr, je výplňová čára spojena s obvodovým perimetrem pouze na jedné straně a délka odebraného obvodového perimetru je omezena na infill_anchor, ale ne delší než tento parametr. Nastavením tohoto parametru na nulu ukotvení zakážete." + #: src/slic3r/GUI/Tab.cpp:4046 msgid "Connection of the support sticks and junctions" msgstr "Spojení podpůrných tyčí a spojek" @@ -1755,12 +1742,8 @@ msgid "Copying of the temporary G-code to the output G-code failed" msgstr "Kopírování dočasného G-codu do výstupního G-codu selhalo" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:163 -msgid "" -"Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\n" -"Error message: %1%" -msgstr "" -"Kopírování dočasného G-codu do výstupního G-codu se nezdařilo. Není SD karta chráněná proti zápisu?\n" -"Chybová hláška: %1%" +msgid "Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%" +msgstr "Kopírování dočasného G-codu do výstupního G-codu se nezdařilo. Není SD karta chráněná proti zápisu?\nChybová hláška: %1%" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:147 msgid "Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp." @@ -1861,7 +1844,7 @@ msgid "CURL init has failed. PrusaSlicer will be unable to establish network con msgstr "CURL init selhal. PrusaSlicer nebude schopen navázat síťová připojení. Další podrobnosti najdete v logu." #: src/slic3r/GUI/wxExtensions.cpp:624 -#, c-format +#, possible-c-format msgid "Current mode is %s" msgstr "Aktuální režim je %s" @@ -2097,7 +2080,7 @@ msgid "Delete Object" msgstr "Smazat Objekt" #: src/slic3r/GUI/GUI_ObjectSettings.cpp:104 -#, c-format +#, possible-c-format msgid "Delete Option %s" msgstr "Odebrání parametru %s" @@ -2359,15 +2342,9 @@ msgid "Do not rearrange the given models before merging and keep their original msgstr "Nepřeuspořádávejte modely před sloučením a tím ponecháním jejich původních souřadnic v XY." #: src/slic3r/GUI/Field.cpp:288 -#, c-format -msgid "" -"Do you mean %s%% instead of %s %s?\n" -"Select YES if you want to change this value to %s%%, \n" -"or NO if you are sure that %s %s is a correct value." -msgstr "" -"Myslíte %s%% namísto %s %s?\n" -"Vyberte ANO, pokud chcete změnit tuto hodnotu na %s%%,\n" -"nebo NE, pokud jste si jisti, že %s %s je správná hodnota." +#, possible-c-format +msgid "Do you mean %s%% instead of %s %s?\nSelect YES if you want to change this value to %s%%, \nor NO if you are sure that %s %s is a correct value." +msgstr "Myslíte %s%% namísto %s %s?\nVyberte ANO, pokud chcete změnit tuto hodnotu na %s%%,\nnebo NE, pokud jste si jisti, že %s %s je správná hodnota." #: src/slic3r/GUI/DoubleSlider.cpp:2138 msgid "Do you want to delete all saved tool changes?" @@ -2529,7 +2506,7 @@ msgid "Eject SD card / Flash drive after the G-code was exported to it." msgstr "Vysunout SD kartu / Flash disk po vyexportování G-codu." #: src/slic3r/GUI/Plater.cpp:2034 -#, c-format +#, possible-c-format msgid "Ejecting of device %s(%s) has failed." msgstr "Vysunutí zařízení %s(%s) se nezdařilo." @@ -2586,6 +2563,14 @@ msgstr "Zapnout ironing" msgid "Enable ironing of the top layers with the hot print head for smooth surface" msgstr "Pro hladké vrchní vrstvy povolte ironing pomocí ohřáté tiskové hlavy" +#: src/slic3r/GUI/GLCanvas3D.cpp:3901 +msgid "Enable rotations (slow)" +msgstr "Povolit rotace (pomalé)" + +#: src/slic3r/GUI/Preferences.cpp:207 +msgid "Enable support for legacy 3DConnexion devices" +msgstr "Povit podporu pro starší zařízení 3DConnexion" + #: src/libslic3r/PrintConfig.cpp:2009 msgid "Enable support material generation." msgstr "Zapne generování podpěr." @@ -2686,6 +2671,10 @@ msgstr "Zadejte počet kopií:" msgid "Enter the temperature needed for extruding your filament." msgstr "Zadejte požadovanou teplotu pro extruzi vašeho filamentu." +#: src/libslic3r/PrintConfig.cpp:813 +msgid "Enter weight of the empty filament spool. One may weigh a partially consumed filament spool before printing and one may compare the measured weight with the calculated weight of the filament with the spool to find out whether the amount of filament on the spool is sufficient to finish the print." +msgstr "Zadejte hmotnost prázdné cívky. Díky tomu budete moci určit, zda máte na cívce dostatečné množství filamentu pro dokončení tisku. Zvážíte cívku s částečně spotřebovaným filamentem a hodnotu porovnáte s vypočtenou hmotností vypočítanou PrusaSlicerem. " + #: src/libslic3r/PrintConfig.cpp:797 msgid "Enter your filament cost per kg here. This is only for statistical information." msgstr "Zde zadejte cenu filamentu za kg. Slouží pouze pro statistické informace." @@ -2712,7 +2701,7 @@ msgid "Error" msgstr "Chyba" #: src/slic3r/GUI/FirmwareDialog.cpp:645 -#, c-format +#, possible-c-format msgid "Error accessing port at %s: %s" msgstr "Chyba při přístupu k portu na %s : %s" @@ -2721,12 +2710,12 @@ msgid "Error during reload" msgstr "Chyba při opětovném načtení souboru" #: src/slic3r/GUI/Plater.cpp:5172 -#, c-format +#, possible-c-format msgid "Error exporting 3MF file %s" msgstr "Chyba při exportu souboru 3MF %s" #: src/slic3r/GUI/Plater.cpp:5138 -#, c-format +#, possible-c-format msgid "Error exporting AMF file %s" msgstr "Chyba při exportu souboru AMF %s" @@ -2773,7 +2762,7 @@ msgid "ERROR:" msgstr "CHYBA:" #: src/slic3r/GUI/FirmwareDialog.cpp:647 -#, c-format +#, possible-c-format msgid "Error: %s" msgstr "Chyba: %s" @@ -2808,7 +2797,7 @@ msgid "Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm" msgstr "Příliš velká hodnota proměnné %1% =%2% mm pro tisk s průměrem trysky %3% mm" #: src/slic3r/GUI/UpdateDialogs.cpp:191 src/slic3r/GUI/UpdateDialogs.cpp:246 -#, c-format +#, possible-c-format msgid "Exit %s" msgstr "Ukončit %s" @@ -3066,7 +3055,7 @@ msgstr "Extruder" #: src/slic3r/GUI/DoubleSlider.cpp:1263 src/slic3r/GUI/DoubleSlider.cpp:1297 #: src/slic3r/GUI/GLCanvas3D.cpp:983 src/slic3r/GUI/GUI_ObjectList.cpp:1832 #: src/slic3r/GUI/Tab.cpp:2489 src/libslic3r/GCode/PreviewData.cpp:450 -#, c-format +#, possible-c-format msgid "Extruder %d" msgstr "Extruder %d" @@ -3078,6 +3067,10 @@ msgstr "Extruder (nástroj) se změní na Extruder \"%1%\"" msgid "Extruder changed to" msgstr "Extruder změněn na" +#: src/slic3r/GUI/Tab.cpp:1589 +msgid "Extruder clearance" +msgstr "Kolizní oblast extruderu" + #: src/slic3r/GUI/Tab.cpp:1563 msgid "Extruder clearance (mm)" msgstr "Kolizní oblast extruderu (mm)" @@ -3222,6 +3215,10 @@ msgstr "filament" msgid "Filament and Nozzle Diameters" msgstr "Průměr filamentu a trysky" +#: src/slic3r/GUI/Plater.cpp:1189 +msgid "Filament at extruder %1%" +msgstr "Filament v extruderu %1%" + #: src/slic3r/GUI/ConfigWizard.cpp:1349 msgid "Filament Diameter:" msgstr "Průměr filamentu:" @@ -3326,10 +3323,22 @@ msgstr "zápis souboru se nezdařil" msgid "Filename" msgstr "Název souboru" +#: src/slic3r/GUI/ConfigWizard.cpp:1181 +msgid "Files association" +msgstr "Asociace souborů" + #: src/libslic3r/PrintConfig.cpp:811 msgid "Fill angle" msgstr "Úhel výplně" +#: src/slic3r/GUI/Plater.cpp:1651 +msgid "Fill bed" +msgstr "Vyplnit podložku" + +#: src/slic3r/GUI/Plater.cpp:3936 +msgid "Fill bed with instances" +msgstr "Vyplnit tiskovou plochu instancemi" + #: src/libslic3r/PrintConfig.cpp:825 msgid "Fill density" msgstr "Hustota výplně" @@ -3350,6 +3359,10 @@ msgstr "Vzor výplně pro obecnou výplň s nízkou hustotou." msgid "Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells." msgstr "Nastavte vzor pro horní výplň. Ovlivňuje pouze horní viditelnou vrstvu a ne její sousední plné vrstvy." +#: src/slic3r/GUI/Plater.cpp:3936 +msgid "Fill the remaining area of bed with instances of the selected object" +msgstr "Vyplní zbývající tiskovou plochu instancemi vybraného objektu" + #: src/slic3r/GUI/BonjourDialog.cpp:225 msgid "Finished" msgstr "Dokončeno" @@ -3465,26 +3478,16 @@ msgstr "Pouze pro vynucené podpěry" #. TRN Description for "WHITE BULLET" #: src/slic3r/GUI/Tab.cpp:3702 -msgid "" -"for the left button: indicates a non-system (or non-default) preset,\n" -"for the right button: indicates that the settings hasn't been modified." -msgstr "" -"na levé straně: indikuje nesystémové (jiné než výchozí) přednastavení,\n" -"na pravé straně: indikuje, že nastavení nebylo změněno." +msgid "for the left button: indicates a non-system (or non-default) preset,\nfor the right button: indicates that the settings hasn't been modified." +msgstr "na levé straně: indikuje nesystémové (jiné než výchozí) přednastavení,\nna pravé straně: indikuje, že nastavení nebylo změněno." #: src/slic3r/GUI/ConfigManipulation.cpp:135 -msgid "" -"For the Wipe Tower to work with the soluble supports, the support layers\n" -"need to be synchronized with the object layers." -msgstr "" -"U čistící věže pokud pracujte s rozpustnými materiály, je třeba\n" -"synchronizovat vrstvy podpěr s vrstvami objektů." +msgid "For the Wipe Tower to work with the soluble supports, the support layers\nneed to be synchronized with the object layers." +msgstr "U čistící věže pokud pracujte s rozpustnými materiály, je třeba\nsynchronizovat vrstvy podpěr s vrstvami objektů." #: src/libslic3r/Print.cpp:1422 msgid "For the Wipe Tower to work with the soluble supports, the support layers need to be synchronized with the object layers." -msgstr "" -"U čistící věže pokud pracujte s rozpustnými materiály, je třeba\n" -"synchronizovat vrstvy podpěr s vrstvami objektů." +msgstr "U čistící věže pokud pracujte s rozpustnými materiály, je třeba\nsynchronizovat vrstvy podpěr s vrstvami objektů." #: src/libslic3r/PrintConfig.cpp:3028 msgid "Force pad around object everywhere" @@ -3522,17 +3525,17 @@ msgstr "Pohled zepředu" msgid "full profile name" msgstr "celé jméno profilu" +#: src/libslic3r/PrintConfig.cpp:817 +msgid "g" +msgstr "g" + #: src/slic3r/GUI/MainFrame.cpp:1527 msgid "G-code" msgstr "G-code" #: src/slic3r/GUI/DoubleSlider.cpp:1146 -msgid "" -"G-code associated to this tick mark is in a conflict with print mode.\n" -"Editing it will cause changes of Slider data." -msgstr "" -"G-code na této značce je v rozporu s tiskovým režimem.\n" -"Editace způsobí změny v posuvníku." +msgid "G-code associated to this tick mark is in a conflict with print mode.\nEditing it will cause changes of Slider data." +msgstr "G-code na této značce je v rozporu s tiskovým režimem.\nEditace způsobí změny v posuvníku." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:165 msgid "G-code file exported to %1%" @@ -3788,7 +3791,7 @@ msgid "Heights at which a filament change is to occur." msgstr "Výšky, při kterých má dojít ke změně filamentu." #: src/slic3r/GUI/ConfigWizard.cpp:451 -#, c-format +#, possible-c-format msgid "Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print." msgstr "Ahoj, vítejte v %su! Tento %s vám pomůže se základní konfigurací; jen několik nastavení a budete připraveni tisknout." @@ -3808,6 +3811,10 @@ msgstr "Nápověda (pro SLA)" msgid "Here you can adjust required purging volume (mm³) for any given pair of tools." msgstr "Zde můžete upravit požadovaný objem čištění (mm³) pro kteroukoliv dvojici extruderů." +#: src/slic3r/GUI/DoubleSlider.cpp:1849 +msgid "Hide ruler" +msgstr "Skrýt pravítko" + #: src/libslic3r/PrintConfig.cpp:1017 msgid "High extruder current on filament swap" msgstr "Zvýšený proud do extruderového motoru při výměně filamentu" @@ -3873,6 +3880,42 @@ msgstr "Plástev" msgid "Horizontal shells" msgstr "Vodorovné stěny" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:209 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:213 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:238 +msgid "Horizontal slider - Move active thumb Left" +msgstr "Horizontální posuvník - Pohyb aktivním ukazatelem vlevo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:210 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:214 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:239 +msgid "Horizontal slider - Move active thumb Right" +msgstr "Horizontální posuvník - Pohyb aktivním ukazatelem vpravo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:238 +msgid "Horizontal slider - Move current thumb Left" +msgstr "Horizontální posuvník - Pohyb posuvníkem vlevo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:239 +msgid "Horizontal slider - Move current thumb Right" +msgstr "Horizontální posuvník - Pohyb posuvníkem vpravo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:236 +msgid "Horizontal slider - Set left thumb as active" +msgstr "Horizontální posuvník -Nastavit levý ukazatel jako aktivní" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:236 +msgid "Horizontal slider - Set left thumb to current thumb" +msgstr "Horizontální posuvník -Pohyb levým posuvníkem k aktivnímu posuvníku" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:237 +msgid "Horizontal slider - Set right thumb as active" +msgstr "Horizontální posuvník -Nastavit pravý ukazatel jako aktivní" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:237 +msgid "Horizontal slider - Set right thumb to current thumb" +msgstr "Horizontální posuvník -Pohyb pravým posuvníkem k aktivnímu posuvníku" + #: src/libslic3r/PrintConfig.cpp:279 msgid "Horizontal width of the brim that will be printed around each object on the first layer." msgstr "Šírka límce který bude vytištěn v první vrstvě okolo každého objektu." @@ -3894,12 +3937,8 @@ msgid "Hostname, IP or URL" msgstr "Název serveru, IP nebo URL" #: src/slic3r/GUI/Tab.cpp:210 -msgid "" -"Hover the cursor over buttons to find more information \n" -"or click this button." -msgstr "" -"Pro více informací přejeďte kurzorem nad tlačítky\n" -"nebo na tlačítko klikněte." +msgid "Hover the cursor over buttons to find more information \nor click this button." +msgstr "Pro více informací přejeďte kurzorem nad tlačítky\nnebo na tlačítko klikněte." #: src/libslic3r/PrintConfig.cpp:2976 msgid "How far should the pad extend around the contained geometry" @@ -3925,6 +3964,10 @@ msgstr "Uplatnění limitů" msgid "How to apply the Machine Limits" msgstr "Jak se mají projevit limity stroje" +#: src/libslic3r/PrintConfig.cpp:163 +msgid "HTTP digest" +msgstr "HTTP digest" + #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:358 #: src/libslic3r/PrintConfig.cpp:113 msgid "HTTPS CA File" @@ -3947,12 +3990,12 @@ msgid "If checked, supports will be generated automatically based on the overhan msgstr "Pokud je zaškrtnuto, budou podpěry generovány automaticky na základě prahové hodnoty převisu. Pokud není zaškrtnuto, bude podpěra generována pouze v místech, kde je umístěn objekt pro \"Vynucení podpěr\"." #: src/slic3r/GUI/ConfigWizard.cpp:1132 -#, c-format +#, possible-c-format msgid "If enabled, %s checks for new application versions online. When a new version becomes available, a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done." msgstr "Pokud je povoleno, kontroluje %s nově dostupné verze. V případě, že je nová verze k dispozici, zobrazí se notifikace při dalším startu programu (nikdy během užívání aplikace). Tento systém slouží pouze pro upozornění uživatele, nedochází k automatické instalaci." #: src/slic3r/GUI/ConfigWizard.cpp:1142 -#, c-format +#, possible-c-format msgid "If enabled, %s downloads updates of built-in system presets in the background.These updates are downloaded into a separate temporary location.When a new preset version becomes available it is offered at application startup." msgstr "Pokud je povoleno, stáhne %s na pozadí aktualizace vestavěných systémových přednastavení. Tyto aktualizace jsou staženy do dočasného umístění. Pokud je k dispozici nové přednastavení, zobrazí se upozornění při startu programu." @@ -3961,12 +4004,8 @@ msgid "If enabled, all printing extruders will be primed at the front edge of th msgstr "Pokud je tato možnost povolena, všechny tiskové extrudery na začátku tisku vytlačí na předním okraji podložky malé množství materiálu." #: src/slic3r/GUI/ConfigWizard.cpp:1164 -msgid "" -"If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" -"If not enabled, the Reload from disk command will ask to select each file using an open file dialog." -msgstr "" -"Pokud je povoleno, v případě vyžádání, umožňuje funkci „Znovu načíst z disku“ automaticky vyhledat a načíst soubory.\n" -"Pokud není povoleno, funkce „Znovu načíst z disku“ požádá o zadání cest ke každému souboru pomocí dialogového okna." +msgid "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\nIf not enabled, the Reload from disk command will ask to select each file using an open file dialog." +msgstr "Pokud je povoleno, v případě vyžádání, umožňuje funkci „Znovu načíst z disku“ automaticky vyhledat a načíst soubory.\nPokud není povoleno, funkce „Znovu načíst z disku“ požádá o zadání cest ke každému souboru pomocí dialogového okna." #: src/slic3r/GUI/Preferences.cpp:91 msgid "If enabled, allows the Reload from disk command to automatically find and load the files when invoked." @@ -3988,6 +4027,18 @@ msgstr "Pokud je povoleno, vykreslí objekt za pomoci mapy prostředí." msgid "If enabled, reverses the direction of zoom with mouse wheel" msgstr "Pokud je povoleno, při zoomu obrátí funkci kolečka myši" +#: src/slic3r/GUI/Preferences.cpp:93 +msgid "If enabled, sets PrusaSlicer as default application to open .3mf files." +msgstr "Pokud je zašktnuto, PrusaSlicer bude výchozí aplikaci pro otevírání souborů .3mf." + +#: src/slic3r/GUI/Preferences.cpp:100 +msgid "If enabled, sets PrusaSlicer as default application to open .stl files." +msgstr "Pokud je zašktnuto, PrusaSlicer bude výchozí aplikaci pro otevírání souborů .stl." + +#: src/slic3r/GUI/Preferences.cpp:179 +msgid "If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files." +msgstr "Pokud je zašktnuto, PrusaSlicer bude výchozí aplikaci pro otevírání souborů .gcode." + #: src/slic3r/GUI/Preferences.cpp:99 msgid "If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup." msgstr "Pokud je povoleno, stáhne Slic3r na pozadí aktualizace vestavěných systémových přednastavení. Tyto aktualizace jsou staženy do dočasného umístění. Pokud je k dispozici nové přednastavení, zobrazí se upozornění při startu programu." @@ -4004,6 +4055,14 @@ msgstr "Pokud je povoleno, bude v pravém horním rohu 3D scény zobrazeno tlač msgid "If enabled, the command line arguments are sent to an existing instance of GUI PrusaSlicer, or an existing PrusaSlicer window is activated. Overrides the \"single_instance\" configuration value from application preferences." msgstr "Pokud je povoleno, argumenty příkazového řádku se odešlou do existující instance grafického uživatelského rozhraní PrusaSlicer,u nebo se aktivuje existující okno PrusaSlicer. Přepíše hodnotu konfigurace „single_instance“ z nastavení aplikace." +#: src/slic3r/GUI/Preferences.cpp:278 +msgid "If enabled, the descriptions of configuration parameters in settings tabs woldn't work as hyperlinks. If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks." +msgstr "Pokud je aškrtnuto, popisky konfiguračních parametrů na kartách nastavení nebudou fungovat jako hypertextové odkazy. Pokud není zaškrtnuto, popisy konfiguračních parametrů budou fungovat jako hypertextové odkazy." + +#: src/slic3r/GUI/Preferences.cpp:209 +msgid "If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M" +msgstr "Pokud je povoleno, je dialogové okno nastavení pro starší zařízení 3DConnexion k dispozici stisknutím kombinace kláves CTRL + M." + #: src/libslic3r/PrintConfig.cpp:1804 msgid "If enabled, the skirt will be as tall as a highest printed object. This is useful to protect an ABS or ASA print from warping and detaching from print bed due to wind draft." msgstr "Pokud je tato možnost povolena, bude obrys (skirt) stejně vysoký jako nejvyšší tištěný objekt. To je užitečné k ochraně modelu při tisku z ABS nebo ASA před deformací a odlepením od tiskové podložky v důsledku průvanu." @@ -4193,7 +4252,7 @@ msgid "in" msgstr "in" #: src/slic3r/GUI/GUI_ObjectList.cpp:3885 -#, c-format +#, possible-c-format msgid "In this mode you can select only other %s Items%s" msgstr "V tomto režimu můžete vybrat pouze jinou/jiný %s %s" @@ -4206,7 +4265,7 @@ msgid "Incompatible presets" msgstr "Nekompatibilní předvolby" #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:75 -#, c-format +#, possible-c-format msgid "Incompatible with this %s" msgstr "Nekompatibilní s tímto %s" @@ -4220,12 +4279,8 @@ msgstr "Zvětšit / zmenšit oblast úprav" #. TRN Description for "UNLOCKED LOCK" #: src/slic3r/GUI/Tab.cpp:3695 -msgid "" -"indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\n" -"Click the UNLOCKED LOCK icon to reset all settings for current option group to the system (or default) values." -msgstr "" -"indikuje, že některá nastavení byla změněna a nejsou shodná se systémovými (výchozími) hodnotami pro danou skupinu nastavení.\n" -"Klikněte na ikonu ODEMKNUTÉHO ZÁMKU pro reset všech nastavení aktuální skupiny nastavení na systémové (nebo výchozí) hodnoty." +msgid "indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\nClick the UNLOCKED LOCK icon to reset all settings for current option group to the system (or default) values." +msgstr "indikuje, že některá nastavení byla změněna a nejsou shodná se systémovými (výchozími) hodnotami pro danou skupinu nastavení.\nKlikněte na ikonu ODEMKNUTÉHO ZÁMKU pro reset všech nastavení aktuální skupiny nastavení na systémové (nebo výchozí) hodnoty." #. TRN Description for "LOCKED LOCK" #: src/slic3r/GUI/Tab.cpp:3691 @@ -4234,12 +4289,8 @@ msgstr "indikuje, že nastavení jsou stejná jako systémové (výchozí) hodno #. TRN Description for "BACK ARROW" #: src/slic3r/GUI/Tab.cpp:3707 -msgid "" -"indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" -"Click the BACK ARROW icon to reset all settings for the current option group to the last saved preset." -msgstr "" -"indikuje, že došlo ke změně nastavení, které není shodné s naposledy uloženým přednastavením pro aktuální skupinu nastavení.\n" -"Klikněte na ikonu ŠIPKY ZPĚT pro reset všech nastavení pro aktuální skupinu nastavení na naposledy uložené přednastavení." +msgid "indicates that the settings were changed and are not equal to the last saved preset for the current option group.\nClick the BACK ARROW icon to reset all settings for the current option group to the last saved preset." +msgstr "indikuje, že došlo ke změně nastavení, které není shodné s naposledy uloženým přednastavením pro aktuální skupinu nastavení.\nKlikněte na ikonu ŠIPKY ZPĚT pro reset všech nastavení pro aktuální skupinu nastavení na naposledy uložené přednastavení." #: src/slic3r/GUI/ConfigManipulation.cpp:210 #: src/slic3r/GUI/GUI_ObjectList.cpp:35 src/slic3r/GUI/GUI_ObjectList.cpp:93 @@ -4314,7 +4365,7 @@ msgstr "Zkontrolovat / aktivovat zálohy konfigurace" #: src/slic3r/GUI/ObjectDataViewModel.cpp:62 #: src/slic3r/GUI/ObjectDataViewModel.cpp:218 -#, c-format +#, possible-c-format msgid "Instance %d" msgstr "Instance %d" @@ -4460,11 +4511,31 @@ msgstr "Rozkmit (Jitter)" msgid "Jump to height" msgstr "Přechod do výšky" +#: src/slic3r/GUI/DoubleSlider.cpp:1223 +#, possible-c-format +msgid "Jump to height %s\nor Set ruler mode" +msgstr "Přechod do výšky %s\nnebo Nastavení režimu pravítka" + +#: src/slic3r/GUI/DoubleSlider.cpp:1220 +#, possible-c-format +msgid "Jump to height %s\nSet ruler mode\nor Set extruder sequence for the entire print" +msgstr "Přechod do výšky %s\nNastavení režimu pravítka\nnebo Nastavení sekvence extruderů pro celý tisk" + #: src/slic3r/GUI/DoubleSlider.cpp:1075 -#, c-format +#, possible-c-format msgid "Jump to height %s or Set extruder sequence for the entire print" msgstr "Přechod do výšky %s nebo Nastavení sekvence extruderů pro celý tisk" +#: src/slic3r/GUI/DoubleSlider.cpp:1222 +#, possible-c-format +msgid "Jump to height %s or Set ruler mode" +msgstr "Přechod do výšky %s nebo Nastavení režimu pravítka" + +#: src/slic3r/GUI/DoubleSlider.cpp:1220 +#, possible-c-format +msgid "Jump to height %s Set ruler mode\n or Set extruder sequence for the entire print" +msgstr "Přechod do výšky %s Nastavení režimu pravítka nebo Nastavení sekvence extruderů pro celý tisk" + #: src/slic3r/GUI/DoubleSlider.cpp:1071 src/slic3r/GUI/DoubleSlider.cpp:1852 msgid "Jump to move" msgstr "Přechod na pohyb v rámci vrstvy" @@ -4640,6 +4711,10 @@ msgstr "Vzdálenost" msgid "Length of the cooling tube to limit space for cooling moves inside it." msgstr "Délka kovové trubičky určené pro ochlazení a zformování filamentu po vytažení z extruderu." +#: src/libslic3r/PrintConfig.cpp:1068 +msgid "Length of the infill anchor" +msgstr "Délka výplňové kotvy" + #. TRN "Slic3r _is licensed under the_ License" #: src/slic3r/GUI/AboutDialog.cpp:141 msgid "License agreements of all following programs (libraries) are part of application license agreement" @@ -4693,6 +4768,14 @@ msgstr "Načíst konfiguraci ze zadaného souboru. Může být použito vícekr msgid "Load exported configuration file" msgstr "Načíst exportovaný konfigurační soubor" +#: src/slic3r/GUI/Plater.cpp:1543 src/slic3r/GUI/Plater.cpp:4976 +msgid "Load File" +msgstr "Načíst soubor" + +#: src/slic3r/GUI/Plater.cpp:1548 src/slic3r/GUI/Plater.cpp:4981 +msgid "Load Files" +msgstr "Načíst soubory" + #: src/slic3r/GUI/GUI_ObjectList.cpp:2038 msgid "Load Part" msgstr "Přidání části" @@ -4788,6 +4871,10 @@ msgstr "Smyček (minimálně)" msgid "Lower Layer" msgstr "Nižší vrstva" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:219 +msgid "Lower layer" +msgstr "Spodní vrstva" + #: src/slic3r/GUI/Tab.cpp:2346 src/slic3r/GUI/Tab.cpp:2442 #: src/libslic3r/PrintConfig.cpp:1202 src/libslic3r/PrintConfig.cpp:1237 #: src/libslic3r/PrintConfig.cpp:1254 src/libslic3r/PrintConfig.cpp:1271 @@ -5020,6 +5107,10 @@ msgstr "Maximální ryv Y" msgid "Maximum jerk Z" msgstr "Maximální ryv Z" +#: src/libslic3r/PrintConfig.cpp:1095 +msgid "Maximum length of the infill anchor" +msgstr "Maximální délka výplňové kotvy" + #: src/libslic3r/PrintConfig.cpp:2814 msgid "Maximum number of bridges that can be placed on a pillar. Bridges hold support point pinheads and connect to pillars as small branches." msgstr "Maximální počet mostů, které mohou být umístěny na podpěrný sloup. Mosty drží hroty podpěr a připojují se ke sloupům jako malé větve." @@ -5195,7 +5286,7 @@ msgid "Mirror vertically" msgstr "Zrcadlit vertikálně" #: src/slic3r/Utils/AstroBox.cpp:69 src/slic3r/Utils/OctoPrint.cpp:68 -#, c-format +#, possible-c-format msgid "Mismatched type of print host: %s" msgstr "Nesprávný typ tiskového serveru: % s" @@ -5395,6 +5486,14 @@ msgstr "Kolečko myši:" msgid "Move" msgstr "Přesunout" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:255 +msgid "Move active slider thumb Left" +msgstr "Posunout ukazatel aktivního posuvníku vlevo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:256 +msgid "Move active slider thumb Right" +msgstr "Posunout ukazatel aktivního posuvníku vpravo" + #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1219 msgid "Move clipping plane" msgstr "Posunout řezovou rovinu" @@ -5419,6 +5518,16 @@ msgstr "Posunout aktivní posuvník nahoru" msgid "Move drainage hole" msgstr "Posun odtokového otvoru" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:209 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:213 +msgid "Move horizontal slider current thumb Left" +msgstr "Posunout aktivní horizontální posuvník vlevo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:210 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:214 +msgid "Move horizontal slider current thumb Right" +msgstr "Posunout aktivní horizontální posuvník vpravo" + #: src/slic3r/GUI/GLCanvas3D.cpp:3810 msgid "Move Object" msgstr "Posunutí Objektu" @@ -5447,6 +5556,16 @@ msgstr "Posun výběru o 10 mm v kladném směru osy Y" msgid "Move support point" msgstr "Posun podpěrného bodu" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:208 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:212 +msgid "Move vertical slider current thumb Down" +msgstr "Posunout vertikální posuvník dolů" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:207 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:211 +msgid "Move vertical slider current thumb Up" +msgstr "Posunout vertikální posuvník nahoru" + #: src/slic3r/GUI/GCodeViewer.cpp:2492 msgid "Movement" msgstr "Přejezd" @@ -5468,7 +5587,7 @@ msgid "Multi-part object detected" msgstr "Detekován objekt obsahující více částí" #: src/slic3r/GUI/FirmwareDialog.cpp:419 src/slic3r/GUI/FirmwareDialog.cpp:454 -#, c-format +#, possible-c-format msgid "Multiple %s devices found. Please only connect one at a time for flashing." msgstr "Bylo nalezeno více zařízení %s . Během flashování mějte připojené pouze jedno." @@ -5477,14 +5596,8 @@ msgid "Multiple Extruders" msgstr "Více Extruderů" #: src/slic3r/GUI/Plater.cpp:2372 -msgid "" -"Multiple objects were loaded for a multi-material printer.\n" -"Instead of considering them as multiple objects, should I consider\n" -"these files to represent a single object having multiple parts?" -msgstr "" -"Bylo nahráno více objektů pro multi materiálovou tiskárnu.\n" -"Mají být vloženy jako jeden objekt obsahující více částí, \n" -"namísto vložení několika objektů?" +msgid "Multiple objects were loaded for a multi-material printer.\nInstead of considering them as multiple objects, should I consider\nthese files to represent a single object having multiple parts?" +msgstr "Bylo nahráno více objektů pro multi materiálovou tiskárnu.\nMají být vloženy jako jeden objekt obsahující více částí, \nnamísto vložení několika objektů?" #: src/libslic3r/PrintConfig.cpp:3638 msgid "Multiply copies by creating a grid." @@ -5553,7 +5666,7 @@ msgid "New version is available." msgstr "K dispozici je nová verze." #: src/slic3r/GUI/UpdateDialogs.cpp:38 -#, c-format +#, possible-c-format msgid "New version of %s is available" msgstr "Je dostupná nová verze %s" @@ -5636,14 +5749,8 @@ msgid "Note, that this/those printer(s) will be deleted after deleting of the se msgstr "Upozorňujeme, že tato/tyto tiskárny budou odstraněny po odstranění vybraného přednastavení." #: src/slic3r/GUI/Tab.cpp:2039 -msgid "" -"Note: All parameters from this group are moved to the Physical Printer settings (see changelog).\n" -"\n" -"A new Physical Printer profile is created by clicking on the \"cog\" icon right of the Printer profiles combo box, by selecting the \"Add physical printer\" item in the Printer combo box. The Physical Printer profile editor opens also when clicking on the \"cog\" icon in the Printer settings tab. The Physical Printer profiles are being stored into PrusaSlicer/physical_printer directory." -msgstr "" -"Poznámka: Všechna nastavení z této sekce jsou přesunuta do nastavení Fyzické tiskárny (viz changelog).\n" -"\n" -"Nový profil Fyzické tiskárny lze vytvořit kliknutím na ikonu „ozubeného kolečka“ vpravo od pole se seznamem profilů tiskáren a výběrem položky „Přidat fyzickou tiskárnu“. Editor fyzické tiskárny se otevře po kliknutí na ikonu „ozubeného kolečka“ na kartě Nastavení tiskárny. Profily fyzických tiskáren se ukládají do adresáře PrusaSlicer/physical_printer directory." +msgid "Note: All parameters from this group are moved to the Physical Printer settings (see changelog).\n\nA new Physical Printer profile is created by clicking on the \"cog\" icon right of the Printer profiles combo box, by selecting the \"Add physical printer\" item in the Printer combo box. The Physical Printer profile editor opens also when clicking on the \"cog\" icon in the Printer settings tab. The Physical Printer profiles are being stored into PrusaSlicer/physical_printer directory." +msgstr "Poznámka: Všechna nastavení z této sekce jsou přesunuta do nastavení Fyzické tiskárny (viz changelog).\n\nNový profil Fyzické tiskárny lze vytvořit kliknutím na ikonu „ozubeného kolečka“ vpravo od pole se seznamem profilů tiskáren a výběrem položky „Přidat fyzickou tiskárnu“. Editor fyzické tiskárny se otevře po kliknutí na ikonu „ozubeného kolečka“ na kartě Nastavení tiskárny. Profily fyzických tiskáren se ukládají do adresáře PrusaSlicer/physical_printer directory." #: src/slic3r/Utils/AstroBox.cpp:92 msgid "Note: AstroBox version at least 1.1.0 is required." @@ -5832,10 +5939,14 @@ msgid "On OSX there is always only one instance of app running by default. Howev msgstr "Na OSX je ve výchozím nastavení vždy spuštěna pouze jedna instance aplikace. Je však povoleno spouštět více instancí stejné aplikace z příkazového řádku. V takovém případě toto nastavení povolí pouze jednu instanci." #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:359 -#, c-format +#, possible-c-format msgid "On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain." msgstr "V tomto systému používá %s certifikáty HTTPS ze systému Certificate Store nebo Keychain." +#: src/slic3r/GUI/KBShortcutsDialog.cpp:215 +msgid "On/Off one layer mode of the vertical slider" +msgstr "Zapnou/vypnout režim jedné vrstvy vertikálního posuvníku" + #: src/slic3r/GUI/DoubleSlider.cpp:1064 msgid "One layer mode" msgstr "Zobrazení po jedné vrstvě" @@ -5943,7 +6054,7 @@ msgid "Open PrusaSlicer" msgstr "Otevřít PrusaSlicer" #: src/slic3r/GUI/MainFrame.cpp:918 src/slic3r/GUI/MainFrame.cpp:1317 -#, c-format +#, possible-c-format msgid "Open the %s website in your browser" msgstr "Otevřít webovou stránku %s v prohlížeči" @@ -6245,7 +6356,7 @@ msgid "Physical printers" msgstr "Fyzické tiskárny" #: src/slic3r/GUI/ConfigWizard.cpp:1226 -#, c-format +#, possible-c-format msgid "Pick another vendor supported by %s" msgstr "Vyberte si jiného výrobce, který je podporováný programem %s" @@ -6377,6 +6488,10 @@ msgstr "Přednastavení \"%1%\" není kompatibilní s novým profilem tiskárny msgid "Preset with name \"%1%\" already exists and is imcopatible with selected printer." msgstr "Přednastavení s názvem \"%1%\" již existuje a není kompatibilní s vybranou tiskárnou." +#: src/slic3r/GUI/SavePresetDialog.cpp:136 +msgid "Preset with name \"%1%\" already exists and is incopatible with selected printer." +msgstr "Přednastavení s názvem \"%1%\" již existuje a není kompatibilní s vybranou tiskárnou." + #: src/slic3r/GUI/SavePresetDialog.cpp:148 msgid "Preset with name \"%1%\" already exists." msgstr "Přednastavení s názvem \"%1%\" již existuje." @@ -6395,23 +6510,15 @@ msgid "Press to activate selection rectangle" msgstr "Stiskem aktivujete obdélníkový výběr" #: src/slic3r/GUI/KBShortcutsDialog.cpp:155 -msgid "" -"Press to select multiple objects\n" -"or move multiple objects with mouse" -msgstr "" -"Stisknutím vyberte více objektů\n" -"nebo přesuňte více objektů pomocí myši" +msgid "Press to select multiple objects\nor move multiple objects with mouse" +msgstr "Stisknutím vyberte více objektů\nnebo přesuňte více objektů pomocí myši" #: src/slic3r/GUI/KBShortcutsDialog.cpp:221 #: src/slic3r/GUI/KBShortcutsDialog.cpp:222 #: src/slic3r/GUI/KBShortcutsDialog.cpp:231 #: src/slic3r/GUI/KBShortcutsDialog.cpp:232 -msgid "" -"Press to speed up 5 times while moving thumb\n" -"with arrow keys or mouse wheel" -msgstr "" -"5× zrychlíte pohyb posuvníku pomocí\n" -"šipek nebo kolečkem myši" +msgid "Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel" +msgstr "5× zrychlíte pohyb posuvníku pomocí\nšipek nebo kolečkem myši" #: src/slic3r/GUI/KBShortcutsDialog.cpp:212 src/slic3r/GUI/Plater.cpp:4052 #: src/slic3r/GUI/Tab.cpp:2559 @@ -6566,7 +6673,7 @@ msgstr "Tisk s více extrudery různých průměrů trysek. Má-li být podpěra #. TRN "Processing input_file_basename" #: src/slic3r/GUI/MainFrame.cpp:1550 -#, c-format +#, possible-c-format msgid "Processing %s" msgstr "Zpracovávám %s" @@ -6621,10 +6728,8 @@ msgid "PrusaSlicer is closing: Unsaved Changes" msgstr "PrusaSlicer se zavírá: Neuložené změny" #: src/slic3r/GUI/OpenGLManager.cpp:259 -#, c-format -msgid "" -"PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" -"while OpenGL version %s, render %s, vendor %s was detected." +#, possible-c-format +msgid "PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \nwhile OpenGL version %s, render %s, vendor %s was detected." msgstr "PrusaSlicer vyžaduje grafický ovladač s funkčním OpenGL 2.0. Zatímco byla detekována verze OpenGL %s, render %s, výrobce %s." #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:50 @@ -6636,14 +6741,8 @@ msgid "PrusaSlicer will remember your action." msgstr "PrusaSlicer si vaši akci zapamatuje." #: src/slic3r/GUI/ConfigWizard.cpp:1174 -msgid "" -"PrusaSlicer's user interfaces comes in three variants:\n" -"Simple, Advanced, and Expert.\n" -"The Simple mode shows only the most frequently used settings relevant for regular 3D printing. The other two offer progressively more sophisticated fine-tuning, they are suitable for advanced and expert users, respectively." -msgstr "" -"Uživatelské rozhraní PrusaSlicer je k dispozici ve třech variantách:\n" -"Jednoduché, pokročilé a expertní.\n" -"Jednoduchý režim zobrazuje pouze nejčastěji používaná nastavení relevantní pro běžný 3D tisk. Další dva nabízejí detailnější doladění a proto jsou vhodné pro pokročilé a expertní uživatele." +msgid "PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\nThe Simple mode shows only the most frequently used settings relevant for regular 3D printing. The other two offer progressively more sophisticated fine-tuning, they are suitable for advanced and expert users, respectively." +msgstr "Uživatelské rozhraní PrusaSlicer je k dispozici ve třech variantách:\nJednoduché, pokročilé a expertní.\nJednoduchý režim zobrazuje pouze nejčastěji používaná nastavení relevantní pro běžný 3D tisk. Další dva nabízejí detailnější doladění a proto jsou vhodné pro pokročilé a expertní uživatele." #: src/slic3r/GUI/UnsavedChangesDialog.cpp:668 msgid "PrusaSlicer: Don't ask me again" @@ -6689,7 +6788,7 @@ msgstr "Rychlé" #: src/slic3r/GUI/GUI_ObjectList.cpp:1661 #: src/slic3r/GUI/GUI_ObjectList.cpp:1667 #: src/slic3r/GUI/GUI_ObjectList.cpp:2008 -#, c-format +#, possible-c-format msgid "Quick Add Settings (%s)" msgstr "Rychlé přidání nastavení (%s)" @@ -6702,7 +6801,7 @@ msgid "Quick Slice and Save As" msgstr "Rychlé Slicování a Uložit jako" #: src/slic3r/GUI/MainFrame.cpp:1144 src/slic3r/GUI/MainFrame.cpp:1402 -#, c-format +#, possible-c-format msgid "Quit %s" msgstr "Ukončit %s" @@ -6727,14 +6826,8 @@ msgid "Ramming customization" msgstr "Přizpůsobení rapidní extruze" #: src/slic3r/GUI/WipeTowerDialog.cpp:41 -msgid "" -"Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n" -"\n" -"This is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." -msgstr "" -"Rapidní extruze označuje rychlé vytlačení filamentu těsně před jeho výměnou za jiný v multi material tiskárně s jedním extruderem. Účelem je správně vytvarovat konec vysouvaného filamentu tak, aby neblokoval zasunutí nového filamentu a také mohl být sám později opětovně zasunut. Tento proces je důležitý a rozdílné materiály mohou pro získání optimálního tvaru vyžadovat různé rychlosti extruze. Z tohoto důvodu jsou objemové průtoky při rapidní extruzi uživatelsky upravitelné.\n" -"\n" -"Toto nastavení je určeno pro pokročilé uživatele, nesprávné nastavení velmi pravděpodobně povede k zaseknutí filamentu, vybroušení filamentu podávacím kolečkem, atd." +msgid "Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." +msgstr "Rapidní extruze označuje rychlé vytlačení filamentu těsně před jeho výměnou za jiný v multi material tiskárně s jedním extruderem. Účelem je správně vytvarovat konec vysouvaného filamentu tak, aby neblokoval zasunutí nového filamentu a také mohl být sám později opětovně zasunut. Tento proces je důležitý a rozdílné materiály mohou pro získání optimálního tvaru vyžadovat různé rychlosti extruze. Z tohoto důvodu jsou objemové průtoky při rapidní extruzi uživatelsky upravitelné.\n\nToto nastavení je určeno pro pokročilé uživatele, nesprávné nastavení velmi pravděpodobně povede k zaseknutí filamentu, vybroušení filamentu podávacím kolečkem, atd." #: src/slic3r/GUI/WipeTowerDialog.cpp:91 msgid "Ramming line spacing" @@ -6794,7 +6887,7 @@ msgid "Recent projects" msgstr "N&edávné projekty" #: src/slic3r/GUI/PresetHints.cpp:262 -#, c-format +#, possible-c-format msgid "Recommended object thin wall thickness for layer height %.2f and" msgstr "Doporučená tloušťka stěny objektu pro výšku vrstvy %.2f a" @@ -6829,7 +6922,7 @@ msgid "Redo" msgstr "Vpřed" #: src/slic3r/GUI/GLCanvas3D.cpp:4382 -#, c-format +#, possible-c-format msgid "Redo %1$d Action" msgid_plural "Redo %1$d Actions" msgstr[0] "%1$d Akce Vpřed" @@ -7054,22 +7147,22 @@ msgid "Report an I&ssue" msgstr "Nahlá&sit chybu" #: src/slic3r/GUI/MainFrame.cpp:928 src/slic3r/GUI/MainFrame.cpp:1327 -#, c-format +#, possible-c-format msgid "Report an issue on %s" msgstr "Nahlásit chybu v programu %s" #: src/slic3r/Utils/PresetUpdater.cpp:733 -#, c-format +#, possible-c-format msgid "requires max. %s" msgstr "vyžaduje max. %s" #: src/slic3r/Utils/PresetUpdater.cpp:730 -#, c-format +#, possible-c-format msgid "requires min. %s" msgstr "vyžaduje min. %s" #: src/slic3r/Utils/PresetUpdater.cpp:726 -#, c-format +#, possible-c-format msgid "requires min. %s and max. %s" msgstr "vyžaduje min. %s a max. %s" @@ -7253,8 +7346,12 @@ msgstr "Úhel otočení kolem osy Y ve stupních." msgid "Rotation angle around the Z axis in degrees." msgstr "Úhel otočení kolem osy Z ve stupních." +#: src/slic3r/GUI/DoubleSlider.cpp:1836 +msgid "Ruler mode" +msgstr "Režim pravítka" + #: src/slic3r/GUI/GUI_App.cpp:1474 -#, c-format +#, possible-c-format msgid "Run %s" msgstr "Spustit %s" @@ -7286,12 +7383,12 @@ msgid "Save" msgstr "Uložit" #: src/slic3r/GUI/SavePresetDialog.cpp:72 -#, c-format +#, possible-c-format msgid "Save %s as:" msgstr "Uložit %s jako:" #: src/slic3r/GUI/MainFrame.cpp:1527 -#, c-format +#, possible-c-format msgid "Save %s file as:" msgstr "Uložit %s soubor jako:" @@ -7313,7 +7410,7 @@ msgstr "Uložit konfiguraci do zadaného souboru." #. TRN "Save current Settings" #: src/slic3r/GUI/Tab.cpp:203 -#, c-format +#, possible-c-format msgid "Save current %s" msgstr "Uložit stávající %s" @@ -7562,14 +7659,8 @@ msgid "Select what kind of support do you need" msgstr "Vyberte typ podpěr, které potřebujete" #: src/slic3r/GUI/DoubleSlider.cpp:2135 -msgid "" -"Select YES if you want to delete all saved tool changes, \n" -"NO if you want all tool changes switch to color changes, \n" -"or CANCEL to leave it unchanged." -msgstr "" -"Vyberte ANO, pokud chcete odstranit všechny uložené změny nástroje,\n" -"NE, pokud chcete, aby se všechny změny nástroje přepnout na změny barev,\n" -"nebo ZRUŠIT pro ponechání beze změny." +msgid "Select YES if you want to delete all saved tool changes, \nNO if you want all tool changes switch to color changes, \nor CANCEL to leave it unchanged." +msgstr "Vyberte ANO, pokud chcete odstranit všechny uložené změny nástroje,\nNE, pokud chcete, aby se všechny změny nástroje přepnout na změny barev,\nnebo ZRUŠIT pro ponechání beze změny." #: src/slic3r/GUI/Selection.cpp:191 msgid "Selection-Add" @@ -7709,7 +7800,7 @@ msgid "Set number of instances" msgstr "Zadat počet instancí" #: src/slic3r/GUI/Plater.cpp:4860 -#, c-format +#, possible-c-format msgid "Set numbers of copies to %d" msgstr "Nastavení počtu kopií na %d" @@ -7729,6 +7820,10 @@ msgstr "Zvolen příznak Tisknout objekt" msgid "Set Printable Instance" msgstr "Zvolen příznak Tisknout Instanci" +#: src/slic3r/GUI/DoubleSlider.cpp:1836 +msgid "Set ruler mode" +msgstr "Nastavení režimu pravítka" + #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:893 msgid "Set Scale" msgstr "Nastavení měřítka" @@ -7798,12 +7893,8 @@ msgid "Set upper thumb to current slider thumb" msgstr "Aktivovat horní ukazatel aktivního posuvníku" #: src/libslic3r/PrintConfig.cpp:3714 -msgid "" -"Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\n" -"For example. loglevel=2 logs fatal, error and warning level messages." -msgstr "" -"Zvolte úroveň logování: 0:fatalní chyby, 1:chyby, 2:varování, 3:info, 4:ladění, 5:trasování\n" -"Například. loglevel=2 zaznamenává fatální chyby, chyby a varovné zprávy." +msgid "Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\nFor example. loglevel=2 logs fatal, error and warning level messages." +msgstr "Zvolte úroveň logování: 0:fatalní chyby, 1:chyby, 2:varování, 3:info, 4:ladění, 5:trasování\nNapříklad. loglevel=2 zaznamenává fatální chyby, chyby a varovné zprávy." #: src/slic3r/GUI/BedShapeDialog.cpp:292 src/slic3r/GUI/MainFrame.cpp:1969 msgid "Settings" @@ -7888,6 +7979,14 @@ msgstr "Zobrazit dialogové okno při přetažení projektu" msgid "Show error message" msgstr "Zobrazit chybovou hlášku" +#: src/slic3r/GUI/DoubleSlider.cpp:1832 +msgid "Show estimated print time" +msgstr "Zobrazit odhadovanou dobu tisku" + +#: src/slic3r/GUI/DoubleSlider.cpp:1832 +msgid "Show estimated print time on the ruler" +msgstr "Zobrazit odhadovanou dobu tisku na pravítku" + #: src/slic3r/GUI/Preferences.cpp:112 msgid "Show incompatible print and filament presets" msgstr "Zobrazit nekompatibilní přednastavení tisku a filamentu" @@ -7900,6 +7999,14 @@ msgstr "Zobrazit přehled klávesových zkratek" msgid "Show normal mode" msgstr "V normálním režimu" +#: src/slic3r/GUI/DoubleSlider.cpp:1828 +msgid "Show object height" +msgstr "Zobrazit výšku objektu" + +#: src/slic3r/GUI/DoubleSlider.cpp:1828 +msgid "Show object height on the ruler" +msgstr "Zobrazit výšku objektu na pravítku" + #: src/slic3r/GUI/MainFrame.cpp:1294 msgid "Show object/instance labels in 3D scene" msgstr "Zobrazit popisky objektů / instancí ve 3D scéně" @@ -7976,10 +8083,18 @@ msgstr "Zobrazit uživatelský adresář konfigurace (datadir)" msgid "Show/Hide 3Dconnexion devices settings dialog" msgstr "Zobrazit / skrýt dialogové okno nastavení zařízení 3Dconnexion" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:175 +msgid "Show/Hide 3Dconnexion devices settings dialog, if enabled" +msgstr "Zobrazit/skrýt okno s nastavením 3Dconnexion zařízení, pokud je zaškrtnuto" + #: src/slic3r/GUI/KBShortcutsDialog.cpp:216 msgid "Show/Hide Legend & Estimated printing time" msgstr "Zobrazit/skrýt legendu a odhadované tiskové časy" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:216 +msgid "Show/Hide Legend and Estimated printing time" +msgstr "Zobrazit/skrýt legendu a odhadované tiskové časy" + #: src/slic3r/GUI/KBShortcutsDialog.cpp:141 msgid "Show/Hide object/instance labels" msgstr "Zobrazit/skrýt popisky objektů/instancí" @@ -8005,14 +8120,8 @@ msgid "Single Extruder Multi Material" msgstr "MultiMaterial tisk s jedním extrudérem" #: src/slic3r/GUI/Tab.cpp:2101 -msgid "" -"Single Extruder Multi Material is selected, \n" -"and all extruders must have the same diameter.\n" -"Do you want to change the diameter for all extruders to first extruder nozzle diameter value?" -msgstr "" -"Je zvolená Multi Materiálová tiskárna s jedním extruderem,\n" -"a proto všechny extrudery musí mít stejný průměr.\n" -"Chcete nastavit průměry všech extruderových trysek podle průměru prvního extruderu?" +msgid "Single Extruder Multi Material is selected, \nand all extruders must have the same diameter.\nDo you want to change the diameter for all extruders to first extruder nozzle diameter value?" +msgstr "Je zvolená Multi Materiálová tiskárna s jedním extruderem,\na proto všechny extrudery musí mít stejný průměr.\nChcete nastavit průměry všech extruderových trysek podle průměru prvního extruderu?" #: src/slic3r/GUI/Tab.cpp:2476 msgid "Single extruder multimaterial parameters" @@ -8121,6 +8230,10 @@ msgstr "Slic3r může nahrát soubory do tiskového serveru. Toto pole by mělo msgid "Slic3r can upload G-code files to a printer host. This field should contain the hostname, IP address or URL of the printer host instance." msgstr "Slic3r může nahrát soubory G-code do tiskového serveru. Toto pole by mělo obsahovat název serveru (hostname), IP adresu nebo URL tiskového serveru." +#: src/libslic3r/PrintConfig.cpp:100 +msgid "Slic3r can upload G-code files to a printer host. This field should contain the hostname, IP address or URL of the printer host instance. Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL in the following format: https://username:password@your-octopi-address/" +msgstr "Slic3r může nahrávat G-cody do tiskového serveru. Toto pole by mělo obsahovat název hostitele, IP adresu nebo URL tiskového serveru. K tiskovému serveru za HAProxy se zapnutým ověřením basic auth lze přistupovat zadáním uživatelského jména a hesla do adresy URL v následujícím formátu: https://username: password@your-octopi-address/" + #: src/libslic3r/PrintConfig.cpp:1407 msgid "Slic3r will not scale speed down below this speed." msgstr "Slic3r nebude měnit rychlost pod tuto rychlost." @@ -8276,13 +8389,9 @@ msgid "Some G/M-code commands, including temperature control and others, are not msgstr "Některé příkazy G/M-code, včetně řízení teplot a další, nejsou univerzální. Vyberte typ firmware, který používá vaše tiskárna pro dosažení kompatibilního výstupu. Příkazy typu \"No extrusion\" zabraňují PrusaSliceru zcela exportovat jakoukoliv hodnotu extruze." #: src/slic3r/GUI/Plater.cpp:2309 -#, c-format -msgid "" -"Some object(s) in file %s looks like saved in inches.\n" -"Should I consider them as a saved in inches and convert them?" -msgstr "" -"Některé objekty v souboru %s se zdá, že jsou uložené v palcích.\n" -"Mám je považovat za uložené v palcích a převést je?" +#, possible-c-format +msgid "Some object(s) in file %s looks like saved in inches.\nShould I consider them as a saved in inches and convert them?" +msgstr "Některé objekty v souboru %s se zdá, že jsou uložené v palcích.\nMám je považovat za uložené v palcích a převést je?" #: src/slic3r/GUI/GLCanvas3D.cpp:636 msgid "Some objects are not visible." @@ -8304,6 +8413,10 @@ msgstr "Některé objekty mohou být na několika menších podložkách namíst msgid "Some printers or printer setups may have difficulties printing with a variable layer height. Enabled by default." msgstr "Některé tiskárny nebo nastavení tiskárny mohou mít potíže s tiskem s proměnnou výškou vrstvy. Ve výchozím nastavení je zapnuto." +#: src/slic3r/GUI/GLCanvas3D.cpp:3967 +msgid "Spacing" +msgstr "Vzdálenost" + #: src/libslic3r/PrintConfig.cpp:2126 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "Rozteč linií kontaktních vrstev. Nastavte nulu pro získání plných kontaktních vrstev." @@ -8449,6 +8562,10 @@ msgstr "Rozdělit na části" msgid "Split to Parts" msgstr "Rozdělit na Části" +#: src/libslic3r/PrintConfig.cpp:812 +msgid "Spool weight" +msgstr "Hmotnost cívky" + #: src/slic3r/GUI/ConfigWizard.cpp:307 msgid "Standard" msgstr "Běžné" @@ -8483,29 +8600,8 @@ msgid "Start the application" msgstr "Spusťit aplikaci" #: src/slic3r/GUI/GUI_App.cpp:386 -msgid "" -"Starting with %1% 2.3, configuration directory on Linux has changed (according to XDG Base Directory Specification) to \n" -"%2%.\n" -"\n" -"This directory did not exist yet (maybe you run the new version for the first time).\n" -"However, an old %1% configuration directory was detected in \n" -"%3%.\n" -"\n" -"Consider moving the contents of the old directory to the new location in order to access your profiles, etc.\n" -"Note that if you decide to downgrade %1% in future, it will use the old location again.\n" -"\n" -"What do you want to do now?" -msgstr "" -"Počínaje %1% 2.3 se konfigurační adresář v systému Linux změnil (podle specifikace XDG Base Directory) na %2%.\n" -"\n" -"Tento adresář ještě neexistoval (možná používáte novou verzi poprvé).\n" -"Byl však zjištěn starý konfigurační adresář %1% v\n" -"%3%.\n" -"\n" -"Zvažte přesunutí obsahu starého adresáře do nového umístění za účelem přístupu k vašim profilům atd.\n" -"Pamatujte, že pokud se v budoucnu rozhodnete %1% downgradovat, použije znovu staré umístění.\n" -"\n" -"Co chcete udělat?" +msgid "Starting with %1% 2.3, configuration directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\nThis directory did not exist yet (maybe you run the new version for the first time).\nHowever, an old %1% configuration directory was detected in \n%3%.\n\nConsider moving the contents of the old directory to the new location in order to access your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old location again.\n\nWhat do you want to do now?" +msgstr "Počínaje %1% 2.3 se konfigurační adresář v systému Linux změnil (podle specifikace XDG Base Directory) na %2%.\n\nTento adresář ještě neexistoval (možná používáte novou verzi poprvé).\nByl však zjištěn starý konfigurační adresář %1% v\n%3%.\n\nZvažte přesunutí obsahu starého adresáře do nového umístění za účelem přístupu k vašim profilům atd.\nPamatujte, že pokud se v budoucnu rozhodnete %1% downgradovat, použije znovu staré umístění.\n\nCo chcete udělat?" #: src/slic3r/GUI/PrintHostDialogs.cpp:149 msgid "Status" @@ -8528,7 +8624,7 @@ msgid "Stealth mode" msgstr "Tichý režim" #: src/slic3r/GUI/Plater.cpp:5118 -#, c-format +#, possible-c-format msgid "STL file exported to %s" msgstr "Soubor STL exportován do %s" @@ -8541,7 +8637,7 @@ msgid "Success!" msgstr "Úspěch!" #: src/slic3r/GUI/Plater.cpp:2047 -#, c-format +#, possible-c-format msgid "Successfully unmounted. The device %s(%s) can now be safely removed from the computer." msgstr "Odpojení proběhlo úspěšné. Zařízení %s(%s) lze nyní bezpečně odebrat z počítače." @@ -8669,12 +8765,12 @@ msgid "Supports stealth mode" msgstr "Podporuje tichý režim" #: src/slic3r/GUI/ConfigManipulation.cpp:158 -msgid "" -"Supports work better, if the following feature is enabled:\n" -"- Detect bridging perimeters" -msgstr "" -"Podpěry fungují lépe, pokud je povolena funkce:\n" -"- Detekovat perimetry přemostění" +msgid "Supports work better, if the following feature is enabled:\n- Detect bridging perimeters" +msgstr "Podpěry fungují lépe, pokud je povolena funkce:\n- Detekovat perimetry přemostění" + +#: src/slic3r/GUI/DoubleSlider.cpp:1824 +msgid "Supprese show the ruler" +msgstr "Potlačit zobrazení pravítka" #: src/slic3r/GUI/Preferences.cpp:104 msgid "Suppress \" - default - \" presets" @@ -8684,6 +8780,10 @@ msgstr "Potlačit “ - výchozí - “ přednastavení" msgid "Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available." msgstr "Potlačit “ - výchozí - “ přednastavení v nabídkách Tisk / Filament / Tiskárna, jakmile budou k dispozici další platné předvolby." +#: src/slic3r/GUI/Preferences.cpp:276 +msgid "Suppress to open hyperlink in browser" +msgstr "Potlačit otevírání hypertextových odkazů v prohlížeči" + #: src/slic3r/GUI/MainFrame.cpp:1527 msgid "SVG" msgstr "SVG" @@ -8721,7 +8821,7 @@ msgid "Switch to Settings" msgstr "Přepnout do Nastavení" #: src/slic3r/GUI/wxExtensions.cpp:623 -#, c-format +#, possible-c-format msgid "Switch to the %s mode" msgstr "Přepnout do režimu %s" @@ -8730,22 +8830,12 @@ msgid "Switching Presets: Unsaved Changes" msgstr "Přepnutí na jiné přednastavení: Neuložené změny" #: src/slic3r/GUI/GUI_App.cpp:1608 -msgid "" -"Switching the language will trigger application restart.\n" -"You will lose content of the plater." -msgstr "" -"Přepnutím jazyka se aplikace restartuje.\n" -"Ztratíte obsah scény." +msgid "Switching the language will trigger application restart.\nYou will lose content of the plater." +msgstr "Přepnutím jazyka se aplikace restartuje.\nZtratíte obsah scény." #: src/slic3r/GUI/WipeTowerDialog.cpp:365 -msgid "" -"Switching to simple settings will discard changes done in the advanced mode!\n" -"\n" -"Do you want to proceed?" -msgstr "" -"Přepnutím do jednoduchého nastavení ztratíte změny provedené v pokročilém režimu!\n" -"\n" -"Opravdu chcete pokračovat?" +msgid "Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?" +msgstr "Přepnutím do jednoduchého nastavení ztratíte změny provedené v pokročilém režimu!\n\nOpravdu chcete pokračovat?" #: src/slic3r/GUI/Tab.cpp:1332 msgid "symbolic profile name" @@ -8817,15 +8907,13 @@ msgid "The %1% infill pattern is not supposed to work at 100%% density." msgstr "Vzor výplně %1% není určen pro 100%% hustotu výplně." #: src/slic3r/GUI/FirmwareDialog.cpp:548 -#, c-format +#, possible-c-format msgid "The %s device could not have been found" msgstr "Zařízení %s nebylo nalezeno" #: src/slic3r/GUI/FirmwareDialog.cpp:436 -#, c-format -msgid "" -"The %s device was not found.\n" -"If the device is connected, please press the Reset button next to the USB connector ..." +#, possible-c-format +msgid "The %s device was not found.\nIf the device is connected, please press the Reset button next to the USB connector ..." msgstr "Zařízení %s nebylo nalezeno. Pokud je zařízení připojeno, stiskněte tlačítko Reset vedle USB konektoru ..." #: src/slic3r/GUI/Tab.cpp:1238 @@ -8833,10 +8921,7 @@ msgid "The current custom preset will be detached from the parent system preset. msgstr "Aktuální vlastní přednastavení bude odděleno od rodičovského systémového přednastavení." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:925 -msgid "" -"The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" -"Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n" -"once the rotation is embedded into the object coordinates." +msgid "The currently manipulated object is tilted (rotation angles are not multiples of 90°).\nNon-uniform scaling of tilted objects is only possible in the World coordinate system,\nonce the rotation is embedded into the object coordinates." msgstr "Momentálně upravovaný objekt je pootočený (rotační úhly nejsou násobky 90°). Nejednotné škálování nakloněných objektů je ve světových koordinátech možné pouze tehdy, když je informace o rotacích zapsána do koordinátů daného objektu." #: src/libslic3r/PrintConfig.cpp:2890 @@ -8970,14 +9055,8 @@ msgid "The percentage of smaller pillars compared to the normal pillar diameter msgstr "Procentuální velikost menších podpěrných pilířů oproti průměru normálních pilířů. Menší pilíře jsou použity v problematických místech, kam se normální nevejdou." #: src/libslic3r/PrintConfig.cpp:2567 -msgid "" -"The percentage of the bed area. \n" -"If the print area exceeds the specified value, \n" -"then a slow tilt will be used, otherwise - a fast tilt" -msgstr "" -"Procentuálně vyjádřená zabraná tisková plocha.\n" -"Pokud tisk zabere více než je zadaná hodnota,\n" -"bude použit pomalý náklon. V ostatních případech bude použit rychlý náklon" +msgid "The percentage of the bed area. \nIf the print area exceeds the specified value, \nthen a slow tilt will be used, otherwise - a fast tilt" +msgstr "Procentuálně vyjádřená zabraná tisková plocha.\nPokud tisk zabere více než je zadaná hodnota,\nbude použit pomalý náklon. V ostatních případech bude použit rychlý náklon" #: src/slic3r/GUI/Tab.cpp:3430 msgid "The physical printer(s) below is based on the preset, you are going to delete." @@ -9024,22 +9103,12 @@ msgid "The selected object couldn't be split because it contains only one part." msgstr "Vybraný objekt nemůže být rozdělen, protože obsahuje pouze jednu část." #: src/slic3r/GUI/MainFrame.cpp:1003 -msgid "" -"The selected project is no longer available.\n" -"Do you want to remove it from the recent projects list?" -msgstr "" -"Vybraný projekt již není k dispozici.\n" -"Chcete ho odstranit ze seznamu posledních projektů?" +msgid "The selected project is no longer available.\nDo you want to remove it from the recent projects list?" +msgstr "Vybraný projekt již není k dispozici.\nChcete ho odstranit ze seznamu posledních projektů?" #: src/slic3r/GUI/DoubleSlider.cpp:1121 -msgid "" -"The sequential print is on.\n" -"It's impossible to apply any custom G-code for objects printing sequentually.\n" -"This code won't be processed during G-code generation." -msgstr "" -"Sekvenční tisk je zapnutý.\n" -"Není možné použít jakýkoliv vlastní G-kód pro objekty tisknuté sekvenčně.\n" -"Během generování G-kódu nebude tento kód zpracován." +msgid "The sequential print is on.\nIt's impossible to apply any custom G-code for objects printing sequentually.\nThis code won't be processed during G-code generation." +msgstr "Sekvenční tisk je zapnutý.\nNení možné použít jakýkoliv vlastní G-kód pro objekty tisknuté sekvenčně.\nBěhem generování G-kódu nebude tento kód zpracován." #: src/slic3r/GUI/ConfigWizard.cpp:1187 msgid "The size of the object can be specified in inches" @@ -9058,23 +9127,9 @@ msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "Rychlost retrakce (toto nastavení platí pouze pro motor extruderu)." #: src/slic3r/GUI/ConfigManipulation.cpp:80 -#, c-format -msgid "" -"The Spiral Vase mode requires:\n" -"- one perimeter\n" -"- no top solid layers\n" -"- 0% fill density\n" -"- no support material\n" -"- Ensure vertical shell thickness enabled\n" -"- Detect thin walls disabled" -msgstr "" -"Režim Spiral Vase vyžaduje:\n" -"- jeden perimetr\n" -"- žádné horní plné vrstvy\n" -"- 0% hustota výplně\n" -"- bez podpěrného materiálu\n" -"- aktivní volbu „Zajistit tloušťku svislých stěn“\n" -"- neaktivní volbu „Detekce tenkých stěn“" +#, possible-c-format +msgid "The Spiral Vase mode requires:\n- one perimeter\n- no top solid layers\n- 0% fill density\n- no support material\n- Ensure vertical shell thickness enabled\n- Detect thin walls disabled" +msgstr "Režim Spiral Vase vyžaduje:\n- jeden perimetr\n- žádné horní plné vrstvy\n- 0% hustota výplně\n- bez podpěrného materiálu\n- aktivní volbu „Zajistit tloušťku svislých stěn“\n- neaktivní volbu „Detekce tenkých stěn“" #: src/libslic3r/Print.cpp:1263 msgid "The Spiral Vase option can only be used when printing a single object." @@ -9112,35 +9167,20 @@ msgid "The vertical distance between object and support material interface. Sett msgstr "Vertikální vzdálenost mezi objektem a podpěrami. Nastavením tohoto parametru na hodnotu 0 se také zabrání tomu, aby Slic3r použil parametry průtoku a rychlosti pro mosty při tisku první vrstvy objektu." #: src/slic3r/GUI/Tab.cpp:2731 -msgid "" -"The Wipe option is not available when using the Firmware Retraction mode.\n" -"\n" -"Shall I disable it in order to enable Firmware Retraction?" -msgstr "" -"Možnost Očistit není k dispozici při použití režimu retrakcí z firmwaru.\n" -"\n" -"Mám ji deaktivovat, aby bylo možné povolit retrakce z firmwaru?" +msgid "The Wipe option is not available when using the Firmware Retraction mode.\n\nShall I disable it in order to enable Firmware Retraction?" +msgstr "Možnost Očistit není k dispozici při použití režimu retrakcí z firmwaru.\n\nMám ji deaktivovat, aby bylo možné povolit retrakce z firmwaru?" #: src/libslic3r/Print.cpp:1294 msgid "The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)." msgstr "Čistíví Věž v současné době nepodporuje volumetric E (use_volumetric_e = 0)." #: src/slic3r/GUI/ConfigManipulation.cpp:114 -msgid "" -"The Wipe Tower currently supports the non-soluble supports only\n" -"if they are printed with the current extruder without triggering a tool change.\n" -"(both support_material_extruder and support_material_interface_extruder need to be set to 0)." -msgstr "" -"Čistící věž v současné době podporuje pouze nerozpustné podpěry\n" -"pokud jsou vytištěny s aktuálním extrudérem bez spuštění výměny nástroje.\n" -"(jak extruder pro tisk podpor tak extruder pro tisk kontaktních podpěr je třeba nastavit na 0)." +msgid "The Wipe Tower currently supports the non-soluble supports only\nif they are printed with the current extruder without triggering a tool change.\n(both support_material_extruder and support_material_interface_extruder need to be set to 0)." +msgstr "Čistící věž v současné době podporuje pouze nerozpustné podpěry\npokud jsou vytištěny s aktuálním extrudérem bez spuštění výměny nástroje.\n(jak extruder pro tisk podpor tak extruder pro tisk kontaktních podpěr je třeba nastavit na 0)." #: src/libslic3r/Print.cpp:1426 msgid "The Wipe Tower currently supports the non-soluble supports only if they are printed with the current extruder without triggering a tool change. (both support_material_extruder and support_material_interface_extruder need to be set to 0)." -msgstr "" -"Čistící věž v současné době podporuje pouze nerozpustné podpěry\n" -"pokud jsou vytištěny s aktuálním extrudérem bez spuštění výměny nástroje.\n" -"(jak extruder pro tisk podpor tak extruder pro tisk kontaktních podpěr je třeba nastavit na 0)." +msgstr "Čistící věž v současné době podporuje pouze nerozpustné podpěry\npokud jsou vytištěny s aktuálním extrudérem bez spuštění výměny nástroje.\n(jak extruder pro tisk podpor tak extruder pro tisk kontaktních podpěr je třeba nastavit na 0)." #: src/libslic3r/Print.cpp:1296 msgid "The Wipe Tower is currently not supported for multimaterial sequential prints." @@ -9187,45 +9227,29 @@ msgid "There are unprintable objects. Try to adjust support settings to make the msgstr "Nacházejí se zde netisknutelné objekty. Zkuste upravit nastavení podpěr tak, aby bylo možné objekty vytisknout." #: src/slic3r/GUI/DoubleSlider.cpp:1155 -msgid "" -"There is a color change for extruder that has not been used before.\n" -"Check your settings to avoid redundant color changes." -msgstr "" -"Dochází zde ke změně barvy u extruderu, který dosud nebyl použit.\n" -"Zkontrolujte nastavení, abyste se vyhnuli redundantním změnám barev." +msgid "There is a color change for extruder that has not been used before.\nCheck your settings to avoid redundant color changes." +msgstr "Dochází zde ke změně barvy u extruderu, který dosud nebyl použit.\nZkontrolujte nastavení, abyste se vyhnuli redundantním změnám barev." #: src/slic3r/GUI/DoubleSlider.cpp:1149 -msgid "" -"There is a color change for extruder that won't be used till the end of print job.\n" -"This code won't be processed during G-code generation." -msgstr "" -"Dochází zde ke změně barvy u extruderu, který již do konce tisku nebude použit.\n" -"Tento kód nebude během generování G-kódu zpracován." +msgid "There is a color change for extruder that won't be used till the end of print job.\nThis code won't be processed during G-code generation." +msgstr "Dochází zde ke změně barvy u extruderu, který již do konce tisku nebude použit.\nTento kód nebude během generování G-kódu zpracován." #: src/slic3r/GUI/DoubleSlider.cpp:1152 -msgid "" -"There is an extruder change set to the same extruder.\n" -"This code won't be processed during G-code generation." -msgstr "" -"Je zde změna extruderu na ten samý extruder.\n" -"Během generování G-codu nebude tento kód zpracován." +msgid "There is an extruder change set to the same extruder.\nThis code won't be processed during G-code generation." +msgstr "Je zde změna extruderu na ten samý extruder.\nBěhem generování G-codu nebude tento kód zpracován." #: src/libslic3r/GCode.cpp:604 msgid "There is an object with no extrusions on the first layer." msgstr "Je zde objekt, u kterého nedochází k extruzi v první vrstvě." #: src/slic3r/GUI/UpdateDialogs.cpp:225 -#, c-format +#, possible-c-format msgid "This %s version: %s" msgstr "Tento %s verze: %s" #: src/slic3r/GUI/Tab.cpp:1244 -msgid "" -"This action is not revertable.\n" -"Do you want to proceed?" -msgstr "" -"Tato akce je nevratná.\n" -"Chcete pokračovat?" +msgid "This action is not revertable.\nDo you want to proceed?" +msgstr "Tato akce je nevratná.\nChcete pokračovat?" #: src/libslic3r/PrintConfig.cpp:199 msgid "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want." @@ -9300,30 +9324,13 @@ msgid "This file cannot be loaded in a simple mode. Do you want to switch to an msgstr "Tento soubor nelze načíst v jednoduchém režimu. Chcete přepnout do pokročilého režimu?" #: src/slic3r/GUI/Plater.cpp:2319 -msgid "" -"This file contains several objects positioned at multiple heights.\n" -"Instead of considering them as multiple objects, should I consider\n" -"this file as a single object having multiple parts?" -msgstr "" -"Tento soubor obsahuje několik objektů umístěných v různých výškách. Mají být vloženy jako jeden objekt obsahující více částí,\n" -"namísto vložení několika objektů?" +msgid "This file contains several objects positioned at multiple heights.\nInstead of considering them as multiple objects, should I consider\nthis file as a single object having multiple parts?" +msgstr "Tento soubor obsahuje několik objektů umístěných v různých výškách. Mají být vloženy jako jeden objekt obsahující více částí,\nnamísto vložení několika objektů?" #: src/slic3r/GUI/FirmwareDialog.cpp:332 -#, c-format -msgid "" -"This firmware hex file does not match the printer model.\n" -"The hex file is intended for: %s\n" -"Printer reported: %s\n" -"\n" -"Do you want to continue and flash this hex file anyway?\n" -"Please only continue if you are sure this is the right thing to do." -msgstr "" -"Tento hex soubor s firmware neodpovídá modelu tiskárny.\n" -"Soubor hex je určen pro: %s\n" -"Tiskárna oznámila: %s\n" -"\n" -"Chcete i přesto pokračovat a nahrát do tiskárny hex soubor?\n" -"Pokračujte prosím, pouze pokud jste si jisti, že je to správný soubor." +#, possible-c-format +msgid "This firmware hex file does not match the printer model.\nThe hex file is intended for: %s\nPrinter reported: %s\n\nDo you want to continue and flash this hex file anyway?\nPlease only continue if you are sure this is the right thing to do." +msgstr "Tento hex soubor s firmware neodpovídá modelu tiskárny.\nSoubor hex je určen pro: %s\nTiskárna oznámila: %s\n\nChcete i přesto pokračovat a nahrát do tiskárny hex soubor?\nPokračujte prosím, pouze pokud jste si jisti, že je to správný soubor." #: src/libslic3r/PrintConfig.cpp:348 msgid "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time." @@ -9379,7 +9386,7 @@ msgstr "Toto je hodnota akcelerace na kterou se tiskárna vrátí po specifický #: src/libslic3r/PrintConfig.cpp:228 msgid "This is the acceleration your printer will use for bridges. Set zero to disable acceleration control for bridges." -msgstr "Nastavení akcelerace tiskárny při vytváření mostů. Nastavením na nulu vypnete ovládání akcelerace pro mosty." +msgstr "Toto je zrychlení, které vaše tiskárna použije při vytváření mostů. Nastavením na nulu vypnete ovládání akcelerace pro mosty." #: src/libslic3r/PrintConfig.cpp:900 msgid "This is the acceleration your printer will use for first layer. Set zero to disable acceleration control for first layer." @@ -9393,12 +9400,16 @@ msgstr "Toto je zrychlení, které vaše tiskárna použije pro výplň. Nastavt msgid "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters." msgstr "Jedná se o akceleraci, kterou vaše tiskárna použije pro perimetry. Vysoká hodnota, jako je 9000, obvykle dává dobré výsledky, pokud je váš hardware v pořádku. Nastavte nulu pro vypnutí řízení zrychlení pro perimetry." +#: src/libslic3r/PrintConfig.cpp:1582 +msgid "This is the acceleration your printer will use for perimeters. Set zero to disable acceleration control for perimeters." +msgstr "Toto je zrychlení, které vaše tiskárna použije pro perimetry. Nastavením na nulu vypnete ovládání akcelerace pro perimetry." + #: src/libslic3r/PrintConfig.cpp:1435 msgid "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "Průměr trysky extruderu (například: 0.5, 0.35 atd.)" #: src/libslic3r/PrintConfig.cpp:1335 -#, c-format +#, possible-c-format msgid "This is the highest printable layer height for this extruder, used to cap the variable layer height and support layer height. Maximum recommended layer height is 75% of the extrusion width to achieve reasonable inter-layer adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." msgstr "Toto je největší možná výška vrstvy pro tento extruder, který se používá k zakrytí výšky proměnné vrstvy a výšky podpůrné vrstvy. Maximální doporučená výška vrstvy činí 75% šířky vytlačování, aby se dosáhlo přiměřené přilnavosti mezi vrstvami. Pokud je nastavena hodnota 0, je výška vrstvy omezena na 75% průměru trysky." @@ -9415,12 +9426,8 @@ msgid "This matrix describes volumes (in cubic milimetres) required to purge the msgstr "Tato matice popisuje objemy (v kubických milimetrech) nutné k vyčištění nového filamentu na čistící věži pro danou dvojici nástrojů." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:928 -msgid "" -"This operation is irreversible.\n" -"Do you want to proceed?" -msgstr "" -"Tato operace je nevratná.\n" -"Chcete pokračovat?" +msgid "This operation is irreversible.\nDo you want to proceed?" +msgstr "Tato operace je nevratná.\nChcete pokračovat?" #: src/libslic3r/PrintConfig.cpp:1550 msgid "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled." @@ -9487,17 +9494,9 @@ msgid "This vector saves required volumes to change from/to each tool used on th msgstr "Tento vektor ukládá potřebné objemy pro změnu z/na každý extruder používaný na čistící věži. Tyto hodnoty jsou použity pro zjednodušení vytvoření celkových objemů čištění níže." #: src/slic3r/GUI/UpdateDialogs.cpp:216 -#, c-format -msgid "" -"This version of %s is not compatible with currently installed configuration bundles.\n" -"This probably happened as a result of running an older %s after using a newer one.\n" -"\n" -"You may either exit %s and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s." -msgstr "" -"Tato verze %s není kompatibilní se současně nainstalovanými balíčky nastavení.\n" -"Tato situace nejspíše nastala spuštěním starší verze %s po používání novější verze.\n" -"\n" -"Můžete buď ukončit %s a zkusit to znovu s novou verzí, nebo můžete znovu spustit výchozí konfiguraci. Před instalací kompatibilního nastavení s touto verzí %s dojde k vytvoření zálohy současné konfigurace." +#, possible-c-format +msgid "This version of %s is not compatible with currently installed configuration bundles.\nThis probably happened as a result of running an older %s after using a newer one.\n\nYou may either exit %s and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s." +msgstr "Tato verze %s není kompatibilní se současně nainstalovanými balíčky nastavení.\nTato situace nejspíše nastala spuštěním starší verze %s po používání novější verze.\n\nMůžete buď ukončit %s a zkusit to znovu s novou verzí, nebo můžete znovu spustit výchozí konfiguraci. Před instalací kompatibilního nastavení s touto verzí %s dojde k vytvoření zálohy současné konfigurace." #: src/libslic3r/PrintConfig.cpp:2601 msgid "This will apply a gamma correction to the rasterized 2D polygons. A gamma value of zero means thresholding with the threshold in the middle. This behaviour eliminates antialiasing without losing holes in polygons." @@ -9573,10 +9572,14 @@ msgid "To use a custom CA file, please import your CA file into Certificate Stor msgstr "Chcete-li použít vlastní soubor CA, importujte soubor CA do Certificate Store / Keychain." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:271 -#, c-format +#, possible-c-format msgid "Toggle %c axis mirroring" msgstr "Přepnout zrcadlení podle osy %c" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:215 +msgid "Toggle vertical slider one layer mode ON/OFF" +msgstr "Zapnou/vypnout režim jedné vrstvy vertikálního posuvníku" + #: src/libslic3r/miniz_extension.cpp:93 msgid "too many files" msgstr "příliš mnoho souborů" @@ -9657,6 +9660,10 @@ msgstr "Vrchních plných vrstev" msgid "Top View" msgstr "Pohled svrchu" +#: src/libslic3r/PrintConfig.cpp:1211 +msgid "Topmost surface only" +msgstr "Pouze nejvrchnější vrstva" + #: src/slic3r/GUI/WipeTowerDialog.cpp:285 msgid "Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded." msgstr "Celkový objem čištění je spočítán jako součet dvou hodnot níže v závislosti na tom, které extrudery jsou zavedeny/vyjmuty." @@ -9717,13 +9724,9 @@ msgid "Type:" msgstr "Typ:" #: src/slic3r/GUI/OpenGLManager.cpp:275 -#, c-format -msgid "" -"Unable to load the following shaders:\n" -"%s" -msgstr "" -"Nelze načíst následující shadery: \n" -"%s" +#, possible-c-format +msgid "Unable to load the following shaders:\n%s" +msgstr "Nelze načíst následující shadery: \n%s" #: src/slic3r/GUI/Plater.cpp:3233 msgid "Unable to reload:" @@ -9745,7 +9748,7 @@ msgid "Undo" msgstr "Zpět" #: src/slic3r/GUI/GLCanvas3D.cpp:4382 -#, c-format +#, possible-c-format msgid "Undo %1$d Action" msgid_plural "Undo %1$d Actions" msgstr[0] "%1$d Akce Zpět" @@ -9792,18 +9795,12 @@ msgid "UNLOCKED LOCK" msgstr "ODEMČENÝ ZÁMEK" #: src/slic3r/GUI/Tab.cpp:3719 -msgid "" -"UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\n" -"Click to reset all settings for current option group to the system (or default) values." +msgid "UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\nClick to reset all settings for current option group to the system (or default) values." msgstr "Ikona ODEMKNUTÉHO ZÁMKU indikuje, že některá nastavení byla změněna a nejsou shodná se systémovými (výchozími) hodnotami pro danou skupinu nastavení. Klikněte pro reset všech nastavení aktuální skupiny nastavení na systémové hodnoty." #: src/slic3r/GUI/Tab.cpp:3734 -msgid "" -"UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system (or default) value.\n" -"Click to reset current value to the system (or default) value." -msgstr "" -"Ikona ODEMKNUTÉHO ZÁMKU indikuje, že se hodnota změnila a není shodná se systémovou (nebo výchozí) hodnotou.\n" -"Klikněte pro reset současné hodnoty na systémovou hodnotu." +msgid "UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system (or default) value.\nClick to reset current value to the system (or default) value." +msgstr "Ikona ODEMKNUTÉHO ZÁMKU indikuje, že se hodnota změnila a není shodná se systémovou (nebo výchozí) hodnotou.\nKlikněte pro reset současné hodnoty na systémovou hodnotu." #: src/slic3r/GUI/KBShortcutsDialog.cpp:173 msgid "Unselect gizmo or clear selection" @@ -9842,7 +9839,7 @@ msgid "up to" msgstr "až do" #: src/slic3r/GUI/GLCanvas3D.cpp:961 -#, c-format +#, possible-c-format msgid "up to %.2f mm" msgstr "do % .2f mm" @@ -9887,10 +9884,18 @@ msgstr "Nahrávání" msgid "Upper Layer" msgstr "Vyšší vrstva" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:218 +msgid "Upper layer" +msgstr "Horní vrstva" + #: src/slic3r/GUI/DoubleSlider.cpp:1276 msgid "Use another extruder" msgstr "Použít jiný extruder" +#: src/slic3r/GUI/GLCanvas3D.cpp:3959 +msgid "Use CTRL+left mouse key to enter text edit mode:" +msgstr "Pomocí CTRL + levé tlačítko myši přejděte do režimu úprav textu:" + #: src/slic3r/GUI/Preferences.cpp:220 msgid "Use custom size for toolbar icons" msgstr "Použít vlastní velikost ikon na panelu nástrojů" @@ -10062,6 +10067,50 @@ msgstr "verze" msgid "Vertical shells" msgstr "Svislé stěny" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:234 +msgid "Vertical slider - Add color change marker for current layer" +msgstr "Vertikální posuvník - Přidat značku změny barvy pro aktuální vrstvu" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:235 +msgid "Vertical slider - Delete color change marker for current layer" +msgstr "Vertikální posuvník - Odebrat značku změny barvy pro aktuální vrstvu" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:208 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:212 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:231 +msgid "Vertical slider - Move active thumb Down" +msgstr "Vertikální posuvník - Pohyb aktivním ukazatelem dolů" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:207 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:211 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 +msgid "Vertical slider - Move active thumb Up" +msgstr "Vertikální posuvník - Pohyb aktivním ukazatelem nahoru" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:231 +msgid "Vertical slider - Move current thumb Down" +msgstr "Vertikální posuvník - Pohyb posuvníkem dolů" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 +msgid "Vertical slider - Move current thumb Up" +msgstr "Vertikální posuvník - Pohyb posuvníkem nahoru" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:233 +msgid "Vertical slider - Set lower thumb as active" +msgstr "Vertikální posuvník -Nastavit spodní ukazatel jako aktivní" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:233 +msgid "Vertical slider - Set lower thumb to current thumb" +msgstr "Vertikální posuvník - Pohyb spodním posuvníkem k aktivnímu posuvníku" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:232 +msgid "Vertical slider - Set upper thumb as active" +msgstr "Vertikální posuvník -Nastavit horní ukazatel jako aktivní" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:232 +msgid "Vertical slider - Set upper thumb to current thumb" +msgstr "Vertikální posuvník - Pohyb horním posuvníkem k aktivnímu posuvníku" + #: src/slic3r/GUI/GUI_Preview.cpp:265 src/slic3r/GUI/GUI_Preview.cpp:271 msgid "View" msgstr "Zobrazení" @@ -10071,9 +10120,7 @@ msgid "View mode" msgstr "Režim zobrazení" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:666 -msgid "" -"Visit \"Preferences\" and check \"%1%\"\n" -"to be asked about unsaved changes again." +msgid "Visit \"Preferences\" and check \"%1%\"\nto be asked about unsaved changes again." msgstr "Pro zrušení zapamatování jděte do Nastaneví a zaškrtněte \"%1%\"." #: src/libslic3r/PrintConfig.cpp:3553 @@ -10139,12 +10186,12 @@ msgid "Welcome" msgstr "Vítejte" #: src/slic3r/GUI/ConfigWizard.cpp:445 -#, c-format +#, possible-c-format msgid "Welcome to the %s Configuration Assistant" msgstr "Vítejte v %s Konfiguračním Asistentu" #: src/slic3r/GUI/ConfigWizard.cpp:447 -#, c-format +#, possible-c-format msgid "Welcome to the %s Configuration Wizard" msgstr "Vítejte v %s Konfiguračním průvodci" @@ -10256,6 +10303,11 @@ msgstr "bude vypnut." msgid "Will inflate or deflate the sliced 2D polygons according to the sign of the correction." msgstr "Vytvoří offset každé vrstvy v rovině XY. Kladná hodnota - offset směrem ven, plocha polygonu se zvětší. Záporná hodnota - offset směrem dovnitř, plocha polygonu se zmenší." +#: src/slic3r/GUI/GCodeViewer.cpp:2660 src/slic3r/GUI/GCodeViewer.cpp:2663 +#: src/slic3r/GUI/GUI_Preview.cpp:978 +msgid "Wipe" +msgstr "Čištění" + #: src/libslic3r/PrintConfig.cpp:2404 msgid "Wipe into this object" msgstr "Vyčistit do tohoto objektu" @@ -10322,18 +10374,8 @@ msgid "World coordinates" msgstr "Světové souřadnice" #: src/slic3r/GUI/UpdateDialogs.cpp:92 -msgid "" -"Would you like to install it?\n" -"\n" -"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" -"\n" -"Updated configuration bundles:" -msgstr "" -"Přejete si spustit instalaci?\n" -"\n" -"Nejprve bude provedena kompletní záloha nastavení. V případě problémů s novou verzí ji bude možné kdykoliv obnovit.\n" -"\n" -"Aktualizované balíčky nastavení:" +msgid "Would you like to install it?\n\nNote that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n\nUpdated configuration bundles:" +msgstr "Přejete si spustit instalaci?\n\nNejprve bude provedena kompletní záloha nastavení. V případě problémů s novou verzí ji bude možné kdykoliv obnovit.\n\nAktualizované balíčky nastavení:" #: src/libslic3r/miniz_extension.cpp:151 msgid "write calledback failed" @@ -10404,7 +10446,7 @@ msgid "You can't change a type of the last solid part of the object." msgstr "Nelze změnit typ poslední plné části objektu." #: src/slic3r/GUI/Plater.cpp:2352 -#, c-format +#, possible-c-format msgid "You can't to add the object(s) from %s because of one or some of them is(are) multi-part" msgstr "Nemůžete přidat objekt(y) z %s, protože jeden nebo některé z nich je(jsou) vícedílné" @@ -10417,12 +10459,8 @@ msgid "You cannot use non-uniform scaling mode for multiple objects/parts select msgstr "Nemůžete použít nestejnoměrnou změnu měřítka pro více vybraných objektů/částí" #: src/slic3r/GUI/SavePresetDialog.cpp:277 -msgid "" -"You have selected physical printer \"%1%\" \n" -"with related printer preset \"%2%\"" -msgstr "" -"Vybrali jste fyzickou tiskárnu \"%1%\"\n" -"s tiskovým přednastavením \"%2%\"" +msgid "You have selected physical printer \"%1%\" \nwith related printer preset \"%2%\"" +msgstr "Vybrali jste fyzickou tiskárnu \"%1%\"\ns tiskovým přednastavením \"%2%\"" #: src/slic3r/GUI/GUI_App.cpp:1078 msgid "You have the following presets with saved options for \"Print Host upload\"" @@ -10437,7 +10475,7 @@ msgid "You must install a configuration update." msgstr "Je nutné nainstalovat aktualizaci konfigurace." #: src/slic3r/GUI/Preferences.cpp:299 -#, c-format +#, possible-c-format msgid "You need to restart %s to make the changes effective." msgstr "Chcete-li provést změny, musíte restartovat aplikaci %s." @@ -10446,7 +10484,7 @@ msgid "You should to change a name of your printer device. It can't be saved." msgstr "Měli byste změnit název tiskového zařízení. Nemůže být uloženo." #: src/slic3r/GUI/GUI_ObjectList.cpp:3884 -#, c-format +#, possible-c-format msgid "You started your selection with %s Item." msgstr "Začali jste výběr s položkou %s." @@ -10479,24 +10517,12 @@ msgid "Z offset" msgstr "Odsazení Z" #: src/slic3r/GUI/ConfigManipulation.cpp:59 -msgid "" -"Zero first layer height is not valid.\n" -"\n" -"The first layer height will be reset to 0.01." -msgstr "" -"Nulová výška první vrstvy není platná.\n" -"\n" -"Výška první vrstvy bude resetována na 0.01." +msgid "Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01." +msgstr "Nulová výška první vrstvy není platná.\n\nVýška první vrstvy bude resetována na 0.01." #: src/slic3r/GUI/ConfigManipulation.cpp:47 -msgid "" -"Zero layer height is not valid.\n" -"\n" -"The layer height will be reset to 0.01." -msgstr "" -"Nulová výška vrstvy není platná.\n" -"\n" -"Výška vrstvy bude resetována na 0.01." +msgid "Zero layer height is not valid.\n\nThe layer height will be reset to 0.01." +msgstr "Nulová výška vrstvy není platná.\n\nVýška vrstvy bude resetována na 0.01." #: src/libslic3r/PrintConfig.cpp:2831 msgid "Zig-Zag" @@ -10520,12 +10546,8 @@ msgid "Zoom to Bed" msgstr "Pohled na tiskovou plochu" #: src/slic3r/GUI/KBShortcutsDialog.cpp:176 -msgid "" -"Zoom to selected object\n" -"or all objects in scene, if none selected" -msgstr "" -"Pohled na označený objekt, nebo na všechny objekty ve scéně,\n" -"pokud není vybraný žádný objekt" +msgid "Zoom to selected object\nor all objects in scene, if none selected" +msgstr "Pohled na označený objekt, nebo na všechny objekty ve scéně,\npokud není vybraný žádný objekt" #: src/libslic3r/PrintConfig.cpp:241 src/libslic3r/PrintConfig.cpp:816 #: src/libslic3r/PrintConfig.cpp:1748 src/libslic3r/PrintConfig.cpp:1758 diff --git a/resources/localization/de/PrusaSlicer.mo b/resources/localization/de/PrusaSlicer.mo index f2692af20330ca4af8e8d9d062ed3c81de968964..b315e0711661de122e0f838b7049d5bcadee25cb 100644 GIT binary patch delta 65545 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�sQn>7DyiF{BGe7FFAPCl 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

&$mAJ#byXXbj`dhs&OLb=-T0uHoXLJf*tNA=YU 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 J$0tlK|39BIC*}YE delta 53341 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 diff --git a/resources/localization/de/PrusaSlicer_de.po b/resources/localization/de/PrusaSlicer_de.po index 97c615134..1fb4c1aff 100644 --- a/resources/localization/de/PrusaSlicer_de.po +++ b/resources/localization/de/PrusaSlicer_de.po @@ -5,20 +5,11 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.4.2\n" -"Project-Id-Version: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language-Team: \n" +"X-Generator: PhraseApp (phraseapp.com)\n" #: src/slic3r/GUI/Tab.cpp:4124 -msgid "" -"\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" -"To enable \"%1%\", please switch off \"%2%\"" -msgstr "" -"\"%1%\" ist deaktiviert, weil \"%2%\" in der Kategorie \"%3%\" eingeschaltet ist.\n" -"Um \"%1%\" zu aktivieren, schalten Sie bitte \"%2%\" aus." +msgid "\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\nTo enable \"%1%\", please switch off \"%2%\"" +msgstr "\"%1%\" ist deaktiviert, weil \"%2%\" in der Kategorie \"%3%\" eingeschaltet ist.\nUm \"%1%\" zu aktivieren, schalten Sie bitte \"%2%\" aus." #: src/libslic3r/PrintConfig.cpp:249 src/libslic3r/PrintConfig.cpp:828 #: src/libslic3r/PrintConfig.cpp:1148 src/libslic3r/PrintConfig.cpp:1327 @@ -29,7 +20,7 @@ msgid "%" msgstr "%" #: src/slic3r/GUI/GLCanvas3D.cpp:969 -#, c-format +#, possible-c-format msgid "%.2f - %.2f mm" msgstr "%.2f - %.2f mm" @@ -51,140 +42,113 @@ msgid "%1%=%2% mm is too low to be printable at a layer height %3% mm" msgstr "%1%=%2% mm ist zu niedrig, um auf einer Schichthöhe von %3% mm druckbar zu sein" #: src/slic3r/GUI/PresetHints.cpp:228 -#, c-format +#, possible-c-format msgid "%3.2f mm³/s at filament speed %3.2f mm/s." msgstr "%3.2f mm³/s mit einer Filamentgeschwindigkeit von %3.2f mm/s." #: src/slic3r/GUI/Plater.cpp:1061 -#, c-format +#, possible-c-format msgid "%d (%d shells)" msgstr "%d (%d Konturhüllen)" #: src/slic3r/GUI/Plater.cpp:1069 -#, c-format +#, possible-c-format msgid "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges" msgstr "%d degenerierte Flächen, %d Kanten korrigiert, %d Flächen entfernt, %d Flächen hinzugefügt, %d Flächen umgekehrt, %d rückwärtige Kanten" #: src/slic3r/GUI/PresetHints.cpp:269 -#, c-format +#, possible-c-format msgid "%d lines: %.2f mm" msgstr "%d Linien: %.2f mm" #: src/slic3r/GUI/MainFrame.cpp:1728 -#, c-format +#, possible-c-format msgid "%d presets successfully imported." msgstr "%d Voreinstellungen erfolgreich importiert." #: src/slic3r/GUI/GUI_App.cpp:718 -#, c-format -msgid "" -"%s\n" -"Do you want to continue?" -msgstr "" -"%s\n" -"Möchten Sie fortfahren?" +#, possible-c-format +msgid "%s\nDo you want to continue?" +msgstr "%s\nMöchten Sie fortfahren?" #: src/slic3r/GUI/MainFrame.cpp:917 src/slic3r/GUI/MainFrame.cpp:1316 -#, c-format +#, possible-c-format msgid "%s &Website" msgstr "%s &Webseite" #: src/slic3r/GUI/GUI_App.cpp:394 -#, c-format +#, possible-c-format msgid "%s - BREAKING CHANGE" msgstr "%s - BREAKING CHANGE" +#: src/slic3r/GUI/Plater.cpp:1410 +#, possible-c-format +msgid "%s - Drop project file" +msgstr "%s - Drop Projektdatei" + #: src/slic3r/GUI/UpdateDialogs.cpp:211 -#, c-format +#, possible-c-format msgid "%s configuration is incompatible" msgstr "%s Konfiguration ist nicht kompatibel" #: src/slic3r/GUI/Field.cpp:223 -#, c-format +#, possible-c-format msgid "%s doesn't support percentage" msgstr "%s akzeptiert keine Prozentangaben" #: src/slic3r/GUI/MsgDialog.cpp:73 -#, c-format +#, possible-c-format msgid "%s error" msgstr "%s Fehler" #: src/slic3r/GUI/ConfigWizard.cpp:499 -#, c-format +#, possible-c-format msgid "%s Family" msgstr "%s Familie" #: src/slic3r/GUI/MsgDialog.cpp:74 -#, c-format +#, possible-c-format msgid "%s has encountered an error" msgstr "%s ist auf einen Fehler gestoßen" #: src/slic3r/GUI/GUI_App.cpp:528 -#, c-format -msgid "" -"%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it.\n" -"\n" -"The application will now terminate." -msgstr "" -"%s ist auf einen Fehler gestoßen. Es wurde wahrscheinlich dadurch verursacht, dass der Speicher knapp wird. Wenn Sie sicher sind, dass Sie genügend RAM auf Ihrem System haben, kann dies auch ein Programmfehler sein, und wir würden uns freuen, wenn Sie ihn melden würden.\n" -"\n" -"Die Anwendung wird nun beendet." +#, possible-c-format +msgid "%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it.\n\nThe application will now terminate." +msgstr "%s ist auf einen Fehler gestoßen. Es wurde wahrscheinlich dadurch verursacht, dass der Speicher knapp wird. Wenn Sie sicher sind, dass Sie genügend RAM auf Ihrem System haben, kann dies auch ein Programmfehler sein, und wir würden uns freuen, wenn Sie ihn melden würden.\n\nDie Anwendung wird nun beendet." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:62 -#, c-format +#, possible-c-format msgid "%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it." msgstr "%s ist auf einen Fehler gestoßen. Es wurde wahrscheinlich dadurch verursacht, dass der Speicher knapp wird. Wenn Sie sicher sind, dass Sie genügend RAM auf Ihrem System haben, kann dies auch ein Programmfehler sein, und wir würden uns freuen, wenn Sie ihn melden würden." #: src/slic3r/GUI/UpdateDialogs.cpp:309 -#, c-format +#, possible-c-format msgid "%s has no configuration updates available." msgstr "Für %s sind keine Konfigurationsaktualisierungen verfügbar." #: src/slic3r/GUI/UpdateDialogs.cpp:148 src/slic3r/GUI/UpdateDialogs.cpp:210 -#, c-format +#, possible-c-format msgid "%s incompatibility" msgstr "%s-Inkompatibilität" #: src/slic3r/GUI/UpdateDialogs.cpp:270 -#, c-format -msgid "" -"%s now uses an updated configuration structure.\n" -"\n" -"So called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\n" -"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n" -"\n" -"Please proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." -msgstr "" -"%s verwendet nun eine aktualisierte Konfigurationsstruktur.\n" -"\n" -"Sogenannte 'Systemeinstellungen' wurden eingeführt; diese enthalten die eingebauten Standardeinstellungen für verschiedene Drucker. Diese Systemeinstellungen können nicht verändert werden. Stattdessen können Benutzer nun ihre eigenen Voreinstellungen erstellen, die Werte von einer der Systemeinstellungen übernehmen.\n" -"Eine übernehmende Voreinstellung kann entweder einen bestimmten Wert von ihrem Vorbild übernehmen, oder ihn mit einem eigenen Wert überschreiben.\n" -"\n" -"Bitte fahren Sie fort mit '%s'. Dies folgt nun, um die neuen Einstellungen einzurichten sowie auszuwählen, ob Einstellungen automatisch aktualisiert werden dürfen." +#, possible-c-format +msgid "%s now uses an updated configuration structure.\n\nSo called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\nAn inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\nPlease proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." +msgstr "%s verwendet nun eine aktualisierte Konfigurationsstruktur.\n\nSogenannte 'Systemeinstellungen' wurden eingeführt; diese enthalten die eingebauten Standardeinstellungen für verschiedene Drucker. Diese Systemeinstellungen können nicht verändert werden. Stattdessen können Benutzer nun ihre eigenen Voreinstellungen erstellen, die Werte von einer der Systemeinstellungen übernehmen.\nEine übernehmende Voreinstellung kann entweder einen bestimmten Wert von ihrem Vorbild übernehmen, oder ihn mit einem eigenen Wert überschreiben.\n\nBitte fahren Sie fort mit '%s'. Dies folgt nun, um die neuen Einstellungen einzurichten sowie auszuwählen, ob Einstellungen automatisch aktualisiert werden dürfen." #: src/slic3r/GUI/GUI_App.cpp:1512 -#, c-format +#, possible-c-format msgid "%s View Mode" msgstr "%s Anzeigemodus" #: src/slic3r/GUI/UpdateDialogs.cpp:151 -#, c-format -msgid "" -"%s will now start updates. Otherwise it won't be able to start.\n" -"\n" -"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" -"\n" -"Updated configuration bundles:" -msgstr "" -"%s beginnt nun mit der Aktualisierung. Andernfalls kann nicht gestartet werden.\n" -"\n" -"Beachten Sie, dass zuerst ein vollständiger Konfigurations-Snapshot erstellt wird. Er kann dann jederzeit wiederhergestellt werden, falls es ein Problem mit der neuen Version geben sollte.\n" -"\n" -"Aktualisierte Konfigurations-Bundles:" +#, possible-c-format +msgid "%s will now start updates. Otherwise it won't be able to start.\n\nNote that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n\nUpdated configuration bundles:" +msgstr "%s beginnt nun mit der Aktualisierung. Andernfalls kann nicht gestartet werden.\n\nBeachten Sie, dass zuerst ein vollständiger Konfigurations-Snapshot erstellt wird. Er kann dann jederzeit wiederhergestellt werden, falls es ein Problem mit der neuen Version geben sollte.\n\nAktualisierte Konfigurations-Bundles:" #: src/slic3r/GUI/MainFrame.cpp:933 src/slic3r/GUI/MainFrame.cpp:937 #: src/slic3r/GUI/MainFrame.cpp:1329 -#, c-format +#, possible-c-format msgid "&About %s" msgstr "Ü&ber %s" @@ -312,6 +276,10 @@ msgstr "&Fenster" msgid "(All)" msgstr "(Alles)" +#: src/slic3r/GUI/Plater.cpp:1195 +msgid "(including spool)" +msgstr "(einschließlich Spule)" + #: src/libslic3r/PrintConfig.cpp:1554 msgid "(minimum)" msgstr "(Minimum)" @@ -328,10 +296,22 @@ msgstr "(Re)Slice jet&zt" msgid "(Unknown)" msgstr "(Unbekannt)" +#: src/slic3r/GUI/Plater.cpp:1195 +msgid "(weight with spool)" +msgstr "(Gewicht mit Spule)" + #: src/slic3r/GUI/MainFrame.cpp:1491 msgid ") not found." msgstr ") nicht gefunden." +#: src/libslic3r/PrintConfig.cpp:1085 +msgid "0 (no open anchors)" +msgstr "0 (keine offenen Anker)" + +#: src/libslic3r/PrintConfig.cpp:1107 +msgid "0 (not anchored)" +msgstr "0 (nicht verankert)" + #: src/libslic3r/PrintConfig.cpp:2060 msgid "0 (soluble)" msgstr "0 (löslich)" @@ -340,6 +320,10 @@ msgstr "0 (löslich)" msgid "0.2 (detachable)" msgstr "0,2 (lösbar)" +#: src/libslic3r/PrintConfig.cpp:1090 src/libslic3r/PrintConfig.cpp:1112 +msgid "1000 (unlimited)" +msgstr "1000 (unbegrenzt)" + #: src/slic3r/GUI/MainFrame.cpp:1234 msgid "3&D" msgstr "3&D" @@ -361,7 +345,7 @@ msgid "3Dconnexion settings" msgstr "3Dconnexion Einstellungen" #: src/slic3r/GUI/Plater.cpp:5167 -#, c-format +#, possible-c-format msgid "3MF file exported to %s" msgstr "3MF Datei exportiert nach %s" @@ -394,7 +378,7 @@ msgid "A toolpath outside the print area was detected." msgstr "Es wurde ein Werkzeugweg außerhalb des Druckbereichs erkannt." #: src/slic3r/GUI/AboutDialog.cpp:212 src/slic3r/GUI/AboutDialog.cpp:215 -#, c-format +#, possible-c-format msgid "About %s" msgstr "Über %s" @@ -403,7 +387,7 @@ msgid "above" msgstr "über" #: src/slic3r/GUI/GLCanvas3D.cpp:965 -#, c-format +#, possible-c-format msgid "above %.2f mm" msgstr "oberhalb %.2f mm" @@ -424,6 +408,10 @@ msgstr "Genauigkeit" msgid "Accurate" msgstr "Akkurat" +#: src/slic3r/GUI/Plater.cpp:1423 +msgid "Action" +msgstr "Aktion" + #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:78 msgid "Activate" msgstr "Aktivieren" @@ -454,7 +442,7 @@ msgstr "Fügt eine Grundschicht unter das gestützte Modell" #: src/libslic3r/PrintConfig.cpp:2200 msgid "Add a sheath (a single perimeter line) around the base support. This makes the support more reliable, but also more difficult to remove." -msgstr "Fügen Sie eine Sheath (eine einzelne Druckkontur) um die Basisschicht herum hinzu. Das macht die Stützstrukturen zuverlässiger, aber auch schwieriger zu entfernen." +msgstr "Fügen Sie eine Sheath (eine einzelne Druckkontur) um die Basisschicht herum hinzu. Das macht die Stützen zuverlässiger, aber auch schwieriger zu entfernen." #: src/slic3r/GUI/DoubleSlider.cpp:1114 msgid "Add another code - Ctrl + Left click" @@ -540,7 +528,7 @@ msgid "Add modifier" msgstr "Modifizierer hinzufügen" #: src/libslic3r/PrintConfig.cpp:515 -#, c-format +#, possible-c-format msgid "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r keeps adding perimeters, until more than 70% of the loop immediately above is supported." msgstr "Fügen Sie bei Bedarf weitere Perimeter hinzu, um Spalten in schrägen Wänden zu vermeiden. PrusaSlicer fügt immer wieder Perimeter hinzu, bis mehr als 70% der unmittelbar darüber liegenden Schleife unterstützt werden." @@ -711,6 +699,10 @@ msgstr "Ausrichten von XY" msgid "Aligned" msgstr "Ausgerichtet" +#: src/libslic3r/PrintConfig.cpp:470 src/libslic3r/PrintConfig.cpp:902 +msgid "Aligned Rectilinear" +msgstr "Ausgerichtet Geradlinig" + #: src/slic3r/GUI/ConfigWizard.cpp:308 src/slic3r/GUI/ConfigWizard.cpp:598 #: src/slic3r/GUI/Tab.cpp:3507 src/slic3r/GUI/UnsavedChangesDialog.cpp:921 msgid "All" @@ -736,10 +728,18 @@ msgstr "Alle Objekte werden entfernt, fortfahren?" msgid "All settings changes will be discarded." msgstr "Alle Einstellungsänderungen werden verworfen." +#: src/libslic3r/PrintConfig.cpp:1212 +msgid "All solid surfaces" +msgstr "Alle massiven Oberflächen" + #: src/slic3r/GUI/ConfigWizard.cpp:307 msgid "All standard" msgstr "Alles standard" +#: src/libslic3r/PrintConfig.cpp:1210 +msgid "All top surfaces" +msgstr "Alle Oberseiten" + #: src/libslic3r/miniz_extension.cpp:121 msgid "allocation failed" msgstr "Allokation fehlgeschlagen" @@ -770,17 +770,13 @@ msgid "Always ask for unsaved changes when selecting new preset" msgstr "Immer nach nicht gespeicherten Änderungen fragen, wenn eine neue Voreinstellung ausgewählt wird" #: src/slic3r/GUI/Plater.cpp:5135 -#, c-format +#, possible-c-format msgid "AMF file exported to %s" msgstr "AMF Datei exportiert nach %s" #: src/slic3r/GUI/GLCanvas3D.cpp:638 -msgid "" -"An object outside the print area was detected.\n" -"Resolve the current problem to continue slicing." -msgstr "" -"Es wurde ein Objekt außerhalb des Druckbereichs erkannt.\n" -"Das Problem lösen, um mit dem Slicen fortzufahren." +msgid "An object outside the print area was detected.\nResolve the current problem to continue slicing." +msgstr "Es wurde ein Objekt außerhalb des Druckbereichs erkannt.\nDas Problem lösen, um mit dem Slicen fortzufahren." #: src/slic3r/GUI/GLCanvas3D.cpp:633 msgid "An object outside the print area was detected." @@ -799,6 +795,10 @@ msgstr "Jeder Pfeil" msgid "Any modifications should be saved as a new preset inherited from this one." msgstr "Alle Änderungen sollten als neue Voreinstellungen gespeichert werden, die von diesem vererbt wurden." +#: src/libslic3r/PrintConfig.cpp:162 +msgid "API key" +msgstr "API Key" + #: src/libslic3r/PrintConfig.cpp:106 msgid "API Key / Password" msgstr "API Key / Kennwort" @@ -829,9 +829,7 @@ msgid "Are you sure you want to %1% the selected preset?" msgstr "Sind Sie sicher, dass Sie die gewählte Voreinstellung %1% möchten?" #: src/slic3r/GUI/FirmwareDialog.cpp:902 -msgid "" -"Are you sure you want to cancel firmware flashing?\n" -"This could leave your printer in an unusable state!" +msgid "Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!" msgstr "Sind Sie sicher, dass Sie das Flashen der Firmware abbrechen wollen? Dies könnte Ihren Drucker in einen unbrauchbaren Zustand versetzen!" #: src/slic3r/GUI/DoubleSlider.cpp:2122 src/slic3r/GUI/DoubleSlider.cpp:2142 @@ -863,6 +861,10 @@ msgstr "Um das Objekt" msgid "Arrange" msgstr "Anordnen" +#: src/slic3r/GUI/GLCanvas3D.cpp:3889 +msgid "Arrange options" +msgstr "Anordnungsoptionen" + #: src/slic3r/GUI/GLCanvas3D.cpp:4859 src/slic3r/GUI/KBShortcutsDialog.cpp:152 msgid "Arrange selection" msgstr "Auswahl anordnen" @@ -925,6 +927,18 @@ msgstr "Beim Schließen der Anwendung nach ungespeicherten Änderungen fragen" msgid "Ask for unsaved changes when selecting new preset" msgstr "Nach nicht gespeicherten Änderungen fragen, wenn eine neue Voreinstellung ausgewählt wird" +#: src/slic3r/GUI/ConfigWizard.cpp:1183 src/slic3r/GUI/Preferences.cpp:91 +msgid "Associate .3mf files to PrusaSlicer" +msgstr ".3mf-Dateien mit PrusaSlicer verknüpfen" + +#: src/slic3r/GUI/Preferences.cpp:177 +msgid "Associate .gcode files to PrusaSlicer G-code Viewer" +msgstr ".gcode-Dateien mit dem PrusaSlicer G-Code-Viewer verknüpfen" + +#: src/slic3r/GUI/ConfigWizard.cpp:1184 src/slic3r/GUI/Preferences.cpp:98 +msgid "Associate .stl files to PrusaSlicer" +msgstr ".stl-Dateien mit PrusaSlicer verknüpfen" + #: src/slic3r/GUI/GUI_App.cpp:1878 src/slic3r/GUI/Jobs/SLAImportJob.cpp:210 #: src/slic3r/GUI/Plater.cpp:2256 src/slic3r/GUI/Tab.cpp:3189 msgid "Attention!" @@ -936,7 +950,7 @@ msgstr "Autorisierungs-Typ" #: src/libslic3r/PrintConfig.cpp:2013 msgid "Auto generated supports" -msgstr "Stützstrukturen automatisch generieren" +msgstr "Stützen automatisch generieren" #: src/slic3r/GUI/Preferences.cpp:64 msgid "Auto-center parts" @@ -948,12 +962,12 @@ msgid "Auto-generate points" msgstr "Punkte automatisch generieren" #: src/slic3r/GUI/Plater.cpp:1066 -#, c-format +#, possible-c-format msgid "Auto-repaired (%d errors)" msgstr "Auto-Reparatur (%d Fehler)" #: src/slic3r/GUI/GUI_ObjectList.cpp:386 -#, c-format +#, possible-c-format msgid "Auto-repaired (%d errors):" msgstr "Auto-Reparatur (%d Fehler):" @@ -1002,18 +1016,12 @@ msgid "BACK ARROW" msgstr "PFEIL ZURÜCK" #: src/slic3r/GUI/Tab.cpp:3727 -msgid "" -"BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" -"Click to reset all settings for the current option group to the last saved preset." +msgid "BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\nClick to reset all settings for the current option group to the last saved preset." msgstr "Das Symbol PFEIL ZURÜCK zeigt an, dass die Einstellungen geändert wurden und nicht mit dem zuletzt gespeicherten Preset für die aktuelle Optionsgruppe übereinstimmen. Klicken Sie hier, um alle Einstellungen für die aktuelle Optionsgruppe auf das zuletzt gespeicherte Preset zurückzusetzen." #: src/slic3r/GUI/Tab.cpp:3741 -msgid "" -"BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" -"Click to reset current value to the last saved preset." -msgstr "" -"Das Symbol PFEIL ZURÜCK zeigt an, dass der Wert geändert wurde und nicht mit dem zuletzt gespeicherten Preset übereinstimmt. \n" -"Klicken Sie, um den aktuellen Wert auf das zuletzt gespeicherte Preset zurückzusetzen." +msgid "BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\nClick to reset current value to the last saved preset." +msgstr "Das Symbol PFEIL ZURÜCK zeigt an, dass der Wert geändert wurde und nicht mit dem zuletzt gespeicherten Preset übereinstimmt. \nKlicken Sie, um den aktuellen Wert auf das zuletzt gespeicherte Preset zurückzusetzen." #: src/slic3r/GUI/Preferences.cpp:72 msgid "Background processing" @@ -1213,24 +1221,16 @@ msgid "buffer too small" msgstr "Puffer zu klein" #: src/slic3r/GUI/GUI_App.cpp:1152 -msgid "" -"But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" -"Settings will be available in physical printers settings." -msgstr "" -"Seit dieser Version von PrusaSlicer zeigen wir diese Informationen nicht mehr in den Druckereinstellungen an.\n" -"Die Einstellungen sind in den Einstellungen für physische Drucker verfügbar." +msgid "But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\nSettings will be available in physical printers settings." +msgstr "Seit dieser Version von PrusaSlicer zeigen wir diese Informationen nicht mehr in den Druckereinstellungen an.\nDie Einstellungen sind in den Einstellungen für physische Drucker verfügbar." #: src/slic3r/GUI/ButtonsDescription.cpp:16 msgid "Buttons And Text Colors Description" msgstr "Schaltflächen und Textfarben Beschreibung" #: src/slic3r/GUI/GUI_App.cpp:1084 -msgid "" -"By default new Printer devices will be named as \"Printer N\" during its creation.\n" -"Note: This name can be changed later from the physical printers settings" -msgstr "" -"Standardmäßig werden neue Drucker bei ihrer Erstellung als \"Drucker N\" bezeichnet.\n" -"Hinweis: Dieser Name kann später über die Einstellungen für physische Drucker geändert werden." +msgid "By default new Printer devices will be named as \"Printer N\" during its creation.\nNote: This name can be changed later from the physical printers settings" +msgstr "Standardmäßig werden neue Drucker bei ihrer Erstellung als \"Drucker N\" bezeichnet.\nHinweis: Dieser Name kann später über die Einstellungen für physische Drucker geändert werden." #: src/slic3r/GUI/PresetHints.cpp:222 msgid "by the print profile maximum" @@ -1272,30 +1272,16 @@ msgid "Cannot calculate extrusion width for %1%: Variable \"%2%\" not accessible msgstr "Kann die Extrusionsbreite für %1% nicht berechnen: Variable \"%2%\" nicht zugänglich." #: src/slic3r/GUI/GUI_ObjectList.cpp:3400 -msgid "" -"Cannot insert a new layer range after the current layer range.\n" -"Current layer range overlaps with the next layer range." -msgstr "" -"Es kann kein neuer Schichtenbereich nach dem aktuellen Schichtenbereich eingefügt werden.\n" -"Der aktuelle Schichtenbereich überschneidet sich mit dem nächsten Schichtenbereich." +msgid "Cannot insert a new layer range after the current layer range.\nCurrent layer range overlaps with the next layer range." +msgstr "Es kann kein neuer Schichtenbereich nach dem aktuellen Schichtenbereich eingefügt werden.\nDer aktuelle Schichtenbereich überschneidet sich mit dem nächsten Schichtenbereich." #: src/slic3r/GUI/GUI_ObjectList.cpp:3391 -msgid "" -"Cannot insert a new layer range after the current layer range.\n" -"The next layer range is too thin to be split to two\n" -"without violating the minimum layer height." -msgstr "" -"Es kann kein neuer Schichtenbereich nach dem aktuellen Schichtenbereich eingefügt werden.\n" -"Der nächste Schichtenbereich ist zu schmal, um auf zwei Schichten aufgeteilt zu werden ohne die Mindestschichthöhe zu verletzen." +msgid "Cannot insert a new layer range after the current layer range.\nThe next layer range is too thin to be split to two\nwithout violating the minimum layer height." +msgstr "Es kann kein neuer Schichtenbereich nach dem aktuellen Schichtenbereich eingefügt werden.\nDer nächste Schichtenbereich ist zu schmal, um auf zwei Schichten aufgeteilt zu werden ohne die Mindestschichthöhe zu verletzen." #: src/slic3r/GUI/GUI_ObjectList.cpp:3395 -msgid "" -"Cannot insert a new layer range between the current and the next layer range.\n" -"The gap between the current layer range and the next layer range\n" -"is thinner than the minimum layer height allowed." -msgstr "" -"Es kann kein neuer Schichtenbereich zwischen dem aktuellen und dem nächsten Schichtenbereich eingefügt werden.\n" -"Die Lücke zwischen dem aktuellen und dem nächsten Schichtenbereich ist ist schmaler als die minimal zulässige Schichthöhe." +msgid "Cannot insert a new layer range between the current and the next layer range.\nThe gap between the current layer range and the next layer range\nis thinner than the minimum layer height allowed." +msgstr "Es kann kein neuer Schichtenbereich zwischen dem aktuellen und dem nächsten Schichtenbereich eingefügt werden.\nDie Lücke zwischen dem aktuellen und dem nächsten Schichtenbereich ist ist schmaler als die minimal zulässige Schichthöhe." #: src/slic3r/GUI/SavePresetDialog.cpp:137 msgid "Cannot overwrite a system profile." @@ -1307,7 +1293,7 @@ msgstr "Ein externes Profil kann nicht überschrieben werden." #: src/libslic3r/SLAPrint.cpp:627 msgid "Cannot proceed without support points! Add support points or disable support generation." -msgstr "Ohne Stützpunkte kann nicht weitergearbeitet werden! Fügen Sie Stützpunkte hinzu oder deaktivieren Sie die Stützstruktur-Generierung." +msgstr "Ohne Stützpunkte kann nicht weitergearbeitet werden! Fügen Sie Stützpunkte hinzu oder deaktivieren Sie die Stützen-Generierung." #: src/slic3r/GUI/Tab.cpp:2068 src/slic3r/GUI/UnsavedChangesDialog.cpp:1066 msgid "Capabilities" @@ -1362,7 +1348,7 @@ msgid "Change Extruders" msgstr "Wechsel Extruder" #: src/slic3r/GUI/GUI_ObjectSettings.cpp:157 -#, c-format +#, possible-c-format msgid "Change Option %s" msgstr "Ändere Option %s" @@ -1447,10 +1433,18 @@ msgstr "Kreis" msgid "Circular" msgstr "Kreisförmig" +#: src/slic3r/GUI/GLCanvas3D.cpp:3961 +msgid "Clearance size" +msgstr "Freiraum" + #: src/slic3r/GUI/GLCanvas3D.cpp:5028 src/slic3r/GUI/GLCanvas3D.cpp:5067 msgid "Click right mouse button to open/close History" msgstr "Klicken Sie mit der rechten Maustaste, um die Historie zu öffnen/schließen" +#: src/slic3r/GUI/GLCanvas3D.cpp:4341 +msgid "Click right mouse button to show arrangement options" +msgstr "Klicken Sie mit der rechten Maustaste, um Anordnungsoptionen anzuzeigen" + #: src/slic3r/GUI/GUI_ObjectList.cpp:451 msgid "Click the icon to change the object printable property" msgstr "Klicken Sie auf das Symbol, um die Druckbar-Eigenschaft des Objekts zu ändern" @@ -1518,7 +1512,7 @@ msgid "Color change (\"%1%\") for Extruder %2%" msgstr "Farbwechsel (\"%1%\") für Extruder %2%" #: src/slic3r/GUI/GLCanvas3D.cpp:1001 -#, c-format +#, possible-c-format msgid "Color change for Extruder %d at %.2f mm" msgstr "Farbwechsel für Extruder %d bei %.2f mm" @@ -1635,6 +1629,14 @@ msgstr "Konfigurations-Assistent" msgid "Confirmation" msgstr "Bestätigung" +#: src/libslic3r/PrintConfig.cpp:1070 +msgid "Connect an infill line to an internal perimeter with a short segment of an additional perimeter. If expressed as percentage (example: 15%) it is calculated over infill extrusion width. PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment shorter than infill_anchor_max is found, the infill line is connected to a perimeter segment at just one side and the length of the perimeter segment taken is limited to this parameter, but no longer than anchor_length_max. Set this parameter to zero to disable anchoring perimeters connected to a single infill line." +msgstr "Verbindet eine Infill-Linie mit einem kurzen Segment eines zusätzlichen Perimeters mit einem internen Perimeter. Wenn sie als Prozentsatz ausgedrückt wird (Beispiel: 15%), wird sie über die Breite der Infill-Extrusion berechnet. PrusaSlicer versucht, zwei nahe beieinander liegende Infill-Linien mit einem kurzen Umfangssegment zu verbinden. Wenn kein solches Perimetersegment gefunden wird, das kürzer als infill_anchor_max ist, wird die Infill-Linie nur an einer Seite mit einem Perimetersegment verbunden und die Länge des genommenen Perimetersegments ist auf diesen Parameter begrenzt, aber nicht länger als anchor_length_max. Setzen Sie diesen Parameter auf Null, um die Verankerung von Perimetern zu deaktivieren, die mit einer einzelnen Infill-Linie verbunden sind." + +#: src/libslic3r/PrintConfig.cpp:1097 +msgid "Connect an infill line to an internal perimeter with a short segment of an additional perimeter. If expressed as percentage (example: 15%) it is calculated over infill extrusion width. PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment shorter than this parameter is found, the infill line is connected to a perimeter segment at just one side and the length of the perimeter segment taken is limited to infill_anchor, but no longer than this parameter. Set this parameter to zero to disable anchoring." +msgstr "Verbindet eine Infill-Linie mit einem kurzen Segment eines zusätzlichen Perimeters mit einem internen Perimeter. Wenn sie als Prozentsatz ausgedrückt wird (Beispiel: 15%), wird sie über die Breite der Infill-Extrusion berechnet. PrusaSlicer versucht, zwei nahe beieinander liegende Infill-Linien mit einem kurzen Umfangssegment zu verbinden. Wenn kein solches Perimetersegment gefunden wird, das kürzer als dieser Parameter ist, wird die Infill-Linie nur an einer Seite mit einem Perimetersegment verbunden und die Länge des genommenen Perimetersegments wird auf infill_anchor begrenzt, aber nicht länger als dieser Parameter. Setzen Sie diesen Parameter auf Null, um die Verankerung zu deaktivieren." + #: src/slic3r/GUI/Tab.cpp:4046 msgid "Connection of the support sticks and junctions" msgstr "Verbindung von Stützstäben und Verbindungen" @@ -1740,12 +1742,8 @@ msgid "Copying of the temporary G-code to the output G-code failed" msgstr "Das Kopieren des temporären G-Codes auf den Ausgabe-G-Code ist fehlgeschlagen" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:163 -msgid "" -"Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\n" -"Error message: %1%" -msgstr "" -"Das Kopieren des temporären G-Codes auf den Ausgabe-G-Code ist fehlgeschlagen. Vielleicht ist die SD-Karte schreibgeschützt?\n" -"Fehlermeldung: %1%" +msgid "Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%" +msgstr "Das Kopieren des temporären G-Codes auf den Ausgabe-G-Code ist fehlgeschlagen. Vielleicht ist die SD-Karte schreibgeschützt?\nFehlermeldung: %1%" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:147 msgid "Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp." @@ -1810,7 +1808,7 @@ msgstr "Ressourcen zum Erstellen einer neuen Verbindung konnten nicht bezogen we #: src/libslic3r/PrintConfig.cpp:2101 msgid "Cover the top contact layer of the supports with loops. Disabled by default." -msgstr "Deckt die obere Kontaktschicht der Stützstrukturen mit Schleifen ab. Standardmäßig deaktiviert." +msgstr "Deckt die obere Kontaktschicht der Stützen mit Schleifen ab. Standardmäßig deaktiviert." #: src/libslic3r/PrintConfig.cpp:91 msgid "Cracks smaller than 2x gap closing radius are being filled during the triangle mesh slicing. The gap closing operation may reduce the final print resolution, therefore it is advisable to keep the value reasonably low." @@ -1846,7 +1844,7 @@ msgid "CURL init has failed. PrusaSlicer will be unable to establish network con msgstr "CURL Init ist fehlgeschlagen. PrusaSlicer ist nicht in der Lage, Netzwerkverbindungen herzustellen. Siehe Protokolle für weitere Details." #: src/slic3r/GUI/wxExtensions.cpp:624 -#, c-format +#, possible-c-format msgid "Current mode is %s" msgstr "Aktueller Modus ist %s" @@ -2082,7 +2080,7 @@ msgid "Delete Object" msgstr "Objekt löschen" #: src/slic3r/GUI/GUI_ObjectSettings.cpp:104 -#, c-format +#, possible-c-format msgid "Delete Option %s" msgstr "Lösche Option %s" @@ -2344,15 +2342,9 @@ msgid "Do not rearrange the given models before merging and keep their original msgstr "Die angegebenen Modelle werden vor dem Zusammenführen nicht neu angeordnet und behalten ihre ursprünglichen XY-Koordinaten." #: src/slic3r/GUI/Field.cpp:288 -#, c-format -msgid "" -"Do you mean %s%% instead of %s %s?\n" -"Select YES if you want to change this value to %s%%, \n" -"or NO if you are sure that %s %s is a correct value." -msgstr "" -"Meinen Sie%s anstelle von %s %s?\n" -"Wählen Sie JA, wenn Sie diesen Wert auf %s%% ändern möchten, \n" -"oder NEIN, wenn Sie sicher sind, dass %s %s ein korrekter Wert ist." +#, possible-c-format +msgid "Do you mean %s%% instead of %s %s?\nSelect YES if you want to change this value to %s%%, \nor NO if you are sure that %s %s is a correct value." +msgstr "Meinen Sie%s anstelle von %s %s?\nWählen Sie JA, wenn Sie diesen Wert auf %s%% ändern möchten, \noder NEIN, wenn Sie sicher sind, dass %s %s ein korrekter Wert ist." #: src/slic3r/GUI/DoubleSlider.cpp:2138 msgid "Do you want to delete all saved tool changes?" @@ -2390,6 +2382,10 @@ msgstr "Nicht Anordnen" msgid "Don't notify about new releases any more" msgstr "Keine Benachrichtigung mehr über neue Releases" +#: src/slic3r/GUI/Plater.cpp:1431 +msgid "Don't show again" +msgstr "Nicht mehr anzeigen" + #: src/libslic3r/PrintConfig.cpp:403 msgid "Don't support bridges" msgstr "Brücken nicht unterstützen" @@ -2510,7 +2506,7 @@ msgid "Eject SD card / Flash drive after the G-code was exported to it." msgstr "SD-Karte / Flash-Laufwerk auswerfen, nachdem der G-Code dorthin exportiert wurde." #: src/slic3r/GUI/Plater.cpp:2034 -#, c-format +#, possible-c-format msgid "Ejecting of device %s(%s) has failed." msgstr "Das Auswerfen von Gerät %s(%s) ist fehlgeschlagen." @@ -2567,9 +2563,17 @@ msgstr "Bügeln aktivieren" msgid "Enable ironing of the top layers with the hot print head for smooth surface" msgstr "Ermöglicht das Bügeln der oberen Schichten mit dem heißen Druckkopf für eine glatte Oberfläche" +#: src/slic3r/GUI/GLCanvas3D.cpp:3901 +msgid "Enable rotations (slow)" +msgstr "Rotationen aktivieren (langsam)" + +#: src/slic3r/GUI/Preferences.cpp:207 +msgid "Enable support for legacy 3DConnexion devices" +msgstr "Ältere 3DConnexion-Geräte unterstützen" + #: src/libslic3r/PrintConfig.cpp:2009 msgid "Enable support material generation." -msgstr "Aktiviert Generierung von Stützstrukturen." +msgstr "Aktiviert Generierung von Stützmaterial." #: src/libslic3r/PrintConfig.cpp:1010 msgid "Enable this to add comments into the G-Code labeling print moves with what object they belong to, which is useful for the Octoprint CancelObject plugin. This settings is NOT compatible with Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill." @@ -2603,11 +2607,11 @@ msgstr "Naht erzwingen" #: src/libslic3r/PrintConfig.cpp:2066 msgid "Enforce support for the first" -msgstr "Erzwinge Stützstrukturen bei den ersten" +msgstr "Erzwinge Stützen bei den ersten" #: src/libslic3r/PrintConfig.cpp:2073 msgid "Enforce support for the first n layers" -msgstr "Erzwinge Stützstrukturen bei den ersten n Schichten" +msgstr "Erzwinge Stützen bei den ersten n Schichten" #: src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp:47 msgid "Enforce supports" @@ -2667,6 +2671,10 @@ msgstr "Geben Sie die Anzahl der Kopien ein:" msgid "Enter the temperature needed for extruding your filament." msgstr "Geben Sie die Temperatur ein, die für die Extrusion Ihres Filaments benötigt wird." +#: src/libslic3r/PrintConfig.cpp:813 +msgid "Enter weight of the empty filament spool. One may weigh a partially consumed filament spool before printing and one may compare the measured weight with the calculated weight of the filament with the spool to find out whether the amount of filament on the spool is sufficient to finish the print." +msgstr "Gewicht der leeren Filament-Spule eingeben. Man kann eine teilweise verbrauchte Filament-Spule vor dem Drucken wiegen und das gemessene Gewicht mit dem berechneten Gewicht des Filaments mit der Spule vergleichen, um herauszufinden, ob die Menge des Filaments auf der Spule ausreicht, um den Druck zu beenden." + #: src/libslic3r/PrintConfig.cpp:797 msgid "Enter your filament cost per kg here. This is only for statistical information." msgstr "Geben Sie hier Ihre Filamentkosten pro kg ein. Dies dient ausschließlich statistischen Zwecken." @@ -2693,7 +2701,7 @@ msgid "Error" msgstr "Fehler" #: src/slic3r/GUI/FirmwareDialog.cpp:645 -#, c-format +#, possible-c-format msgid "Error accessing port at %s: %s" msgstr "Fehler beim Zugriff auf Port bei %s:%s" @@ -2702,12 +2710,12 @@ msgid "Error during reload" msgstr "Fehler beim erneuten Laden" #: src/slic3r/GUI/Plater.cpp:5172 -#, c-format +#, possible-c-format msgid "Error exporting 3MF file %s" msgstr "Fehler beim Exportieren der 3MF Datei %s" #: src/slic3r/GUI/Plater.cpp:5138 -#, c-format +#, possible-c-format msgid "Error exporting AMF file %s" msgstr "Fehler beim Exportieren der AMF Datei %s" @@ -2754,7 +2762,7 @@ msgid "ERROR:" msgstr "FEHLER:" #: src/slic3r/GUI/FirmwareDialog.cpp:647 -#, c-format +#, possible-c-format msgid "Error: %s" msgstr "Fehler: %s" @@ -2789,7 +2797,7 @@ msgid "Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm" msgstr "Übermäßig %1%=%2% mm, um mit einem Düsendurchmesser von %3% mm druckbar zu sein" #: src/slic3r/GUI/UpdateDialogs.cpp:191 src/slic3r/GUI/UpdateDialogs.cpp:246 -#, c-format +#, possible-c-format msgid "Exit %s" msgstr "%s beenden" @@ -2891,7 +2899,7 @@ msgstr "Exportiere die aktuelle Plattenbelegung als STL" #: src/slic3r/GUI/MainFrame.cpp:1083 msgid "Export current plate as STL including supports" -msgstr "Exportiert die aktuelle Plattenbelegung als STL einschließlich Stützstrukturen" +msgstr "Exportiert die aktuelle Plattenbelegung als STL einschließlich Stützen" #: src/slic3r/GUI/ConfigWizard.cpp:1160 msgid "Export full pathnames of models and parts sources into 3mf and amf files" @@ -2933,7 +2941,7 @@ msgstr "Exportiere die Plattenbelegung als &STL" #: src/slic3r/GUI/MainFrame.cpp:1083 msgid "Export plate as STL &including supports" -msgstr "Export&iere Plattenbelegung als STL einschließlich Stützstrukturen" +msgstr "Export&iere Plattenbelegung als STL einschließlich Stützen" #: src/libslic3r/PrintConfig.cpp:3525 msgid "Export SLA" @@ -3047,7 +3055,7 @@ msgstr "Extruder" #: src/slic3r/GUI/DoubleSlider.cpp:1263 src/slic3r/GUI/DoubleSlider.cpp:1297 #: src/slic3r/GUI/GLCanvas3D.cpp:983 src/slic3r/GUI/GUI_ObjectList.cpp:1832 #: src/slic3r/GUI/Tab.cpp:2489 src/libslic3r/GCode/PreviewData.cpp:450 -#, c-format +#, possible-c-format msgid "Extruder %d" msgstr "Extruder %d" @@ -3059,9 +3067,13 @@ msgstr "Extruder (Werkzeug) ist geändert auf Extruder \"%1%\"" msgid "Extruder changed to" msgstr "Extruder geändert auf" +#: src/slic3r/GUI/Tab.cpp:1589 +msgid "Extruder clearance" +msgstr "Extruder Freiraum" + #: src/slic3r/GUI/Tab.cpp:1563 msgid "Extruder clearance (mm)" -msgstr "Extruder Abstand (mm)" +msgstr "Extruder Freiraum (mm)" #: src/libslic3r/PrintConfig.cpp:558 msgid "Extruder Color" @@ -3203,6 +3215,10 @@ msgstr "Filament" msgid "Filament and Nozzle Diameters" msgstr "Filament- und Düsendurchmesser" +#: src/slic3r/GUI/Plater.cpp:1189 +msgid "Filament at extruder %1%" +msgstr "Filament auf Extruder %1%" + #: src/slic3r/GUI/ConfigWizard.cpp:1349 msgid "Filament Diameter:" msgstr "Filamentdurchmesser:" @@ -3307,10 +3323,22 @@ msgstr "Schreiben der Datei fehlgeschlagen" msgid "Filename" msgstr "Dateiname" +#: src/slic3r/GUI/ConfigWizard.cpp:1181 +msgid "Files association" +msgstr "Zuordnung der Dateien" + #: src/libslic3r/PrintConfig.cpp:811 msgid "Fill angle" msgstr "Füllwinkel" +#: src/slic3r/GUI/Plater.cpp:1651 +msgid "Fill bed" +msgstr "Bett auffüllen" + +#: src/slic3r/GUI/Plater.cpp:3936 +msgid "Fill bed with instances" +msgstr "Bett mit Kopien auffüllen" + #: src/libslic3r/PrintConfig.cpp:825 msgid "Fill density" msgstr "Fülldichte" @@ -3331,6 +3359,10 @@ msgstr "Füllmuster für allgemeines Infill mit niedriger Dichte." msgid "Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells." msgstr "Füllmuster für die obere Füllung. Dies betrifft nur die obere sichtbare Schicht und nicht die angrenzenden massiven Konturen." +#: src/slic3r/GUI/Plater.cpp:3936 +msgid "Fill the remaining area of bed with instances of the selected object" +msgstr "Den verbleibenden Bereich des Bettes mit Kopien des ausgewählten Objekts auffüllen" + #: src/slic3r/GUI/BonjourDialog.cpp:225 msgid "Finished" msgstr "Fertig" @@ -3446,17 +3478,11 @@ msgstr "Nur für Stützverstärker" #. TRN Description for "WHITE BULLET" #: src/slic3r/GUI/Tab.cpp:3702 -msgid "" -"for the left button: indicates a non-system (or non-default) preset,\n" -"for the right button: indicates that the settings hasn't been modified." -msgstr "" -"Beim linken Knopf: zeigt eine Nicht-System- (oder Nicht-Standard-) Einstellung an.\n" -"Beim rechten Knopf: zeigt an, dass die Einstellung nicht geändert wurde." +msgid "for the left button: indicates a non-system (or non-default) preset,\nfor the right button: indicates that the settings hasn't been modified." +msgstr "Beim linken Knopf: zeigt eine Nicht-System- (oder Nicht-Standard-) Einstellung an.\nBeim rechten Knopf: zeigt an, dass die Einstellung nicht geändert wurde." #: src/slic3r/GUI/ConfigManipulation.cpp:135 -msgid "" -"For the Wipe Tower to work with the soluble supports, the support layers\n" -"need to be synchronized with the object layers." +msgid "For the Wipe Tower to work with the soluble supports, the support layers\nneed to be synchronized with the object layers." msgstr "Damit der Reinigungsturm mit den löslichen Trägermaterialien arbeiten kann, müssen die Stützschichten mit den Objektschichten synchronisiert sein." #: src/libslic3r/Print.cpp:1422 @@ -3499,17 +3525,17 @@ msgstr "Frontalansicht" msgid "full profile name" msgstr "vollständiger Profilname" +#: src/libslic3r/PrintConfig.cpp:817 +msgid "g" +msgstr "g" + #: src/slic3r/GUI/MainFrame.cpp:1527 msgid "G-code" msgstr "G-Code" #: src/slic3r/GUI/DoubleSlider.cpp:1146 -msgid "" -"G-code associated to this tick mark is in a conflict with print mode.\n" -"Editing it will cause changes of Slider data." -msgstr "" -"Der mit diesem Häkchen verbundene G-Code steht in Konflikt mit dem Druckmodus.\n" -"Seine Bearbeitung führt zu Änderungen der Slicer-Daten." +msgid "G-code associated to this tick mark is in a conflict with print mode.\nEditing it will cause changes of Slider data." +msgstr "Der mit diesem Häkchen verbundene G-Code steht in Konflikt mit dem Druckmodus.\nSeine Bearbeitung führt zu Änderungen der Slicer-Daten." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:165 msgid "G-code file exported to %1%" @@ -3552,19 +3578,19 @@ msgstr "Erzeugt nicht weniger als die Anzahl der Schürzenschleifen, die benöti #: src/libslic3r/PrintConfig.cpp:2007 msgid "Generate support material" -msgstr "Generiere Stützstrukturen" +msgstr "Generiere Stützmaterial" #: src/libslic3r/PrintConfig.cpp:2068 msgid "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate." -msgstr "Generiere Stützstrukturen für die angegebene Anzahl von Schichten, die von unten gezählt werden, unabhängig davon, ob normale Stützstrukturen aktiviert sind oder nicht und unabhängig von einer Winkelschwelle. Dies ist nützlich, um die Haftung von Objekten mit einem sehr dünnen oder schlechten Standfuß auf der Bauplatte zu erhöhen." +msgstr "Generiere Stützmaterial für die angegebene Anzahl von Schichten, die von unten gezählt werden, unabhängig davon, ob normales Stützmaterial aktiviert ist oder nicht und unabhängig von einer Winkelschwelle. Dies ist nützlich, um die Haftung von Objekten mit einem sehr dünnen oder schlechten Standfuß auf der Bauplatte zu erhöhen." #: src/libslic3r/PrintConfig.cpp:2756 msgid "Generate supports" -msgstr "Stützstrukturen generieren" +msgstr "Stützen generieren" #: src/libslic3r/PrintConfig.cpp:2758 msgid "Generate supports for the models" -msgstr "Erzeugt Stützstrukturen für die Modelle" +msgstr "Erzeugt Stützen für die Modelle" #: src/slic3r/GUI/Plater.cpp:3554 msgid "generated warnings" @@ -3596,7 +3622,7 @@ msgstr "Generiere Schürze" #: src/libslic3r/PrintObject.cpp:422 msgid "Generating support material" -msgstr "Generiere Stützstrukturen" +msgstr "Generiere Stützmaterial" #: src/libslic3r/SLAPrintSteps.cpp:47 src/libslic3r/SLAPrintSteps.cpp:359 msgid "Generating support points" @@ -3765,7 +3791,7 @@ msgid "Heights at which a filament change is to occur." msgstr "Höhen, bei denen eine Filamentwechsel stattfinden soll." #: src/slic3r/GUI/ConfigWizard.cpp:451 -#, c-format +#, possible-c-format msgid "Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print." msgstr "Hallo, willkommen bei %s! Dieses %s hilft Ihnen bei der Erstkonfiguration; nur ein paar Einstellungen und Sie sind bereit zum Drucken." @@ -3785,6 +3811,10 @@ msgstr "Hilfe (SLA Optionen)" msgid "Here you can adjust required purging volume (mm³) for any given pair of tools." msgstr "Hier können Sie das erforderliche Reinigungsvolumen (mm³) für ein beliebiges Werkzeugpaar einstellen." +#: src/slic3r/GUI/DoubleSlider.cpp:1849 +msgid "Hide ruler" +msgstr "Lineal verbergen" + #: src/libslic3r/PrintConfig.cpp:1017 msgid "High extruder current on filament swap" msgstr "Hohe Extruderstromstärke beim Filamentwechsel" @@ -3850,6 +3880,42 @@ msgstr "Bienenwabe" msgid "Horizontal shells" msgstr "Horizontale Konturhüllen" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:209 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:213 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:238 +msgid "Horizontal slider - Move active thumb Left" +msgstr "Horizontaler Schieberegler - Aktiven Schieber nach links bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:210 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:214 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:239 +msgid "Horizontal slider - Move active thumb Right" +msgstr "Horizontaler Schieberegler - Aktiven Schieber nach rechts bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:238 +msgid "Horizontal slider - Move current thumb Left" +msgstr "Horizontaler Schieberegler - Aktuellen Schieber nach links bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:239 +msgid "Horizontal slider - Move current thumb Right" +msgstr "Horizontaler Schieberegler - Aktuellen Schieber nach rechts bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:236 +msgid "Horizontal slider - Set left thumb as active" +msgstr "Horizontaler Schieberegler - Linken Schieber aktiv setzen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:236 +msgid "Horizontal slider - Set left thumb to current thumb" +msgstr "Horizontaler Schieberegler - Linken Schieber auf aktuellen setzen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:237 +msgid "Horizontal slider - Set right thumb as active" +msgstr "Horizontaler Schieberegler - Rechten Schieber aktiv setzen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:237 +msgid "Horizontal slider - Set right thumb to current thumb" +msgstr "Horizontaler Schieberegler - Rechten Schieber auf aktuellen setzen" + #: src/libslic3r/PrintConfig.cpp:279 msgid "Horizontal width of the brim that will be printed around each object on the first layer." msgstr "Horizontalbreite des Randes, der um jedes Objekt auf der Bodenschicht gedruckt wird." @@ -3871,12 +3937,8 @@ msgid "Hostname, IP or URL" msgstr "Hostname, IP oder URL" #: src/slic3r/GUI/Tab.cpp:210 -msgid "" -"Hover the cursor over buttons to find more information \n" -"or click this button." -msgstr "" -"Bewegen Sie den Mauszeiger über die Schaltflächen, um weitere Informationen zu erhalten,\n" -"oder klicken Sie auf diese Schaltfläche." +msgid "Hover the cursor over buttons to find more information \nor click this button." +msgstr "Bewegen Sie den Mauszeiger über die Schaltflächen, um weitere Informationen zu erhalten,\noder klicken Sie auf diese Schaltfläche." #: src/libslic3r/PrintConfig.cpp:2976 msgid "How far should the pad extend around the contained geometry" @@ -3902,6 +3964,10 @@ msgstr "Wie man Grenzen anwendet" msgid "How to apply the Machine Limits" msgstr "Wie man Maschinengrenzen anwendet" +#: src/libslic3r/PrintConfig.cpp:163 +msgid "HTTP digest" +msgstr "HTTP Digest" + #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:358 #: src/libslic3r/PrintConfig.cpp:113 msgid "HTTPS CA File" @@ -3921,15 +3987,15 @@ msgstr "ID" #: src/libslic3r/PrintConfig.cpp:2015 msgid "If checked, supports will be generated automatically based on the overhang threshold value. If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only." -msgstr "Wenn dieses Kontrollkästchen aktiviert ist, werden Stützstrukturen automatisch basierend auf dem Schwellenwert für den Überhang generiert. Wenn diese Option nicht aktiviert ist, werden Stützen nur innerhalb der Volumen der \"Stützverstärker\" generiert." +msgstr "Wenn dieses Kontrollkästchen aktiviert ist, werden Stützen automatisch basierend auf dem Schwellenwert für den Überhang generiert. Wenn diese Option nicht aktiviert ist, werden Stützen nur innerhalb der Volumen der \"Stützverstärker\" generiert." #: src/slic3r/GUI/ConfigWizard.cpp:1132 -#, c-format +#, possible-c-format msgid "If enabled, %s checks for new application versions online. When a new version becomes available, a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done." msgstr "Falls aktiviert, sucht %s online nach neuen Versionen der Anwendung. Falls eine neue Version verfügbar ist, wird eine Mitteilung beim nächsten Programmstart angezeigt (aber nie während der Programmausführung). Dies dient nur der Mitteilung; es findet keine automatische Installation statt." #: src/slic3r/GUI/ConfigWizard.cpp:1142 -#, c-format +#, possible-c-format msgid "If enabled, %s downloads updates of built-in system presets in the background.These updates are downloaded into a separate temporary location.When a new preset version becomes available it is offered at application startup." msgstr "Wenn aktiviert, lädt %s Updates der eingebauten Systemvoreinstellungen im Hintergrund herunter. Diese Updates werden in einen separaten temporären Speicherort heruntergeladen. Wenn eine neue Voreinstellungsversion verfügbar wird, wird sie beim Programmstart angeboten." @@ -3938,12 +4004,8 @@ msgid "If enabled, all printing extruders will be primed at the front edge of th msgstr "Wenn aktiviert, werden alle Druckextruder zu Beginn des Druckvorgangs an der Vorderkante des Druckbetts geprimt." #: src/slic3r/GUI/ConfigWizard.cpp:1164 -msgid "" -"If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" -"If not enabled, the Reload from disk command will ask to select each file using an open file dialog." -msgstr "" -"Wenn diese Option aktiviert ist, ermöglicht der Befehl Von Festplatte neu laden das automatische Suchen und Laden der Dateien, wenn er aufgerufen wird.\n" -"Wenn nicht aktiviert, fordert der Befehl Von der Festplatte neu laden jede Datei über ein Dialogfeld zum Öffnen von Dateien zur Auswahl auf." +msgid "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\nIf not enabled, the Reload from disk command will ask to select each file using an open file dialog." +msgstr "Wenn diese Option aktiviert ist, ermöglicht der Befehl Von Festplatte neu laden das automatische Suchen und Laden der Dateien, wenn er aufgerufen wird.\nWenn nicht aktiviert, fordert der Befehl Von der Festplatte neu laden jede Datei über ein Dialogfeld zum Öffnen von Dateien zur Auswahl auf." #: src/slic3r/GUI/Preferences.cpp:91 msgid "If enabled, allows the Reload from disk command to automatically find and load the files when invoked." @@ -3965,6 +4027,18 @@ msgstr "Wenn aktiviert, wird das Objekt mit Hilfe der Environment Map gerendert. msgid "If enabled, reverses the direction of zoom with mouse wheel" msgstr "Wenn aktiviert, wird die Richtung des Zooms mit dem Mausrad umgekehrt" +#: src/slic3r/GUI/Preferences.cpp:93 +msgid "If enabled, sets PrusaSlicer as default application to open .3mf files." +msgstr "Wenn aktiviert, legt PrusaSlicer als Standardanwendung fest, um .3mf-Dateien zu öffnen." + +#: src/slic3r/GUI/Preferences.cpp:100 +msgid "If enabled, sets PrusaSlicer as default application to open .stl files." +msgstr "Wenn aktiviert, legt PrusaSlicer als Standardanwendung zum Öffnen von .stl-Dateien fest." + +#: src/slic3r/GUI/Preferences.cpp:179 +msgid "If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files." +msgstr "Wenn aktiviert, legt PrusaSlicer G-Code-Viewer als Standardanwendung zum Öffnen von .gcode-Dateien fest." + #: src/slic3r/GUI/Preferences.cpp:99 msgid "If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup." msgstr "Wenn aktiviert, lädt Slic3r Updates der eingebauten Systemvoreinstellungen im Hintergrund herunter. Diese Updates werden in einen separaten temporären Speicherort heruntergeladen. Wenn eine neue Voreinstellungsversion verfügbar wird, wird sie beim Programmstart angeboten." @@ -3981,6 +4055,14 @@ msgstr "Wenn aktiviert, wird die Schaltfläche zum Zusammenklappen der Seitenlei msgid "If enabled, the command line arguments are sent to an existing instance of GUI PrusaSlicer, or an existing PrusaSlicer window is activated. Overrides the \"single_instance\" configuration value from application preferences." msgstr "Wenn aktiviert, werden die Befehlszeilenargumente an eine vorhandene Instanz der GUI PrusaSlicer gesendet, oder ein vorhandenes PrusaSlicer-Fenster wird aktiviert. Übersteuert den Konfigurationswert \"single_instance\" aus den Anwendungseinstellungen." +#: src/slic3r/GUI/Preferences.cpp:278 +msgid "If enabled, the descriptions of configuration parameters in settings tabs woldn't work as hyperlinks. If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks." +msgstr "Wenn aktiviert, funktionieren die Beschreibungen von Konfigurationsparametern in Einstellungsregistern nicht als Hyperlinks. Wenn diese Option deaktiviert ist, funktionieren die Beschreibungen von Konfigurationsparametern in Einstellungsregistern als Hyperlinks." + +#: src/slic3r/GUI/Preferences.cpp:209 +msgid "If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M" +msgstr "Wenn aktiviert, ist der Einstellungsdialog für ältere 3DConnexion-Geräte durch Drücken von STRG+M verfügbar." + #: src/libslic3r/PrintConfig.cpp:1804 msgid "If enabled, the skirt will be as tall as a highest printed object. This is useful to protect an ABS or ASA print from warping and detaching from print bed due to wind draft." msgstr "Wenn diese Option aktiviert ist, wird die Schürze so hoch wie das höchste gedruckte Objekt sein. Dies ist nützlich, um einen ABS- oder ASA-Druck vor dem Verziehen und Ablösen vom Druckbett aufgrund von Zugluft zu schützen." @@ -4094,10 +4176,18 @@ msgstr "Importiere Konfiguration von &Projekt" msgid "Import Config from ini/amf/3mf/gcode" msgstr "Konfiguration aus ini/amf/3mf/gcode importieren" +#: src/slic3r/GUI/Plater.cpp:1419 +msgid "Import config only" +msgstr "Nur Konfiguration importieren" + #: src/slic3r/GUI/Jobs/SLAImportJob.cpp:39 msgid "Import file" msgstr "Datei importieren" +#: src/slic3r/GUI/Plater.cpp:1418 +msgid "Import geometry only" +msgstr "Nur Geometrie importieren" + #: src/slic3r/GUI/Jobs/SLAImportJob.cpp:46 msgid "Import model and profile" msgstr "Modell und Profil importieren" @@ -4162,7 +4252,7 @@ msgid "in" msgstr "in" #: src/slic3r/GUI/GUI_ObjectList.cpp:3885 -#, c-format +#, possible-c-format msgid "In this mode you can select only other %s Items%s" msgstr "In diesem Modus wählen Sie nur andere %s Elemente%s" @@ -4175,7 +4265,7 @@ msgid "Incompatible presets" msgstr "Inkompatible Voreinstellungen" #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:75 -#, c-format +#, possible-c-format msgid "Incompatible with this %s" msgstr "Nicht kompatibel mit diesem %s" @@ -4189,12 +4279,8 @@ msgstr "Bearbeitungsbereich vergrößern/verkleinern" #. TRN Description for "UNLOCKED LOCK" #: src/slic3r/GUI/Tab.cpp:3695 -msgid "" -"indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\n" -"Click the UNLOCKED LOCK icon to reset all settings for current option group to the system (or default) values." -msgstr "" -"zeigt an, dass einige Einstellungen geändert wurden und nicht mit den System- (oder Standard-) Werten für die aktuelle Optionsgruppe übereinstimmen.\n" -"Klicken Sie auf das Symbol GEÖFFNETES SCHLOSS, um alle Einstellungen für die aktuelle Optionsgruppe auf die System- (oder Standard-) Werte zurückzusetzen." +msgid "indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\nClick the UNLOCKED LOCK icon to reset all settings for current option group to the system (or default) values." +msgstr "zeigt an, dass einige Einstellungen geändert wurden und nicht mit den System- (oder Standard-) Werten für die aktuelle Optionsgruppe übereinstimmen.\nKlicken Sie auf das Symbol GEÖFFNETES SCHLOSS, um alle Einstellungen für die aktuelle Optionsgruppe auf die System- (oder Standard-) Werte zurückzusetzen." #. TRN Description for "LOCKED LOCK" #: src/slic3r/GUI/Tab.cpp:3691 @@ -4203,12 +4289,8 @@ msgstr "zeigt an, dass die Einstellungen mit den System- (oder Standard-) Werten #. TRN Description for "BACK ARROW" #: src/slic3r/GUI/Tab.cpp:3707 -msgid "" -"indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" -"Click the BACK ARROW icon to reset all settings for the current option group to the last saved preset." -msgstr "" -"zeigt an, dass die Einstellungen geändert wurden und nicht mit dem zuletzt gespeicherten Preset für die aktuelle Optionsgruppe übereinstimmen.\n" -"Klicken Sie auf das Symbol PFEIL ZURÜCK, um alle Einstellungen für die aktuelle Optionsgruppe auf das zuletzt gespeicherte Preset zurückzusetzen." +msgid "indicates that the settings were changed and are not equal to the last saved preset for the current option group.\nClick the BACK ARROW icon to reset all settings for the current option group to the last saved preset." +msgstr "zeigt an, dass die Einstellungen geändert wurden und nicht mit dem zuletzt gespeicherten Preset für die aktuelle Optionsgruppe übereinstimmen.\nKlicken Sie auf das Symbol PFEIL ZURÜCK, um alle Einstellungen für die aktuelle Optionsgruppe auf das zuletzt gespeicherte Preset zurückzusetzen." #: src/slic3r/GUI/ConfigManipulation.cpp:210 #: src/slic3r/GUI/GUI_ObjectList.cpp:35 src/slic3r/GUI/GUI_ObjectList.cpp:93 @@ -4283,7 +4365,7 @@ msgstr "Inspiziere / aktiviere Konfigurations-Momentaufnahmen" #: src/slic3r/GUI/ObjectDataViewModel.cpp:62 #: src/slic3r/GUI/ObjectDataViewModel.cpp:218 -#, c-format +#, possible-c-format msgid "Instance %d" msgstr "Kopie %d" @@ -4429,11 +4511,31 @@ msgstr "Jitter" msgid "Jump to height" msgstr "Zur Höhe wechseln" +#: src/slic3r/GUI/DoubleSlider.cpp:1223 +#, possible-c-format +msgid "Jump to height %s\nor Set ruler mode" +msgstr "Auf Höhe %s springen\noder Linealmodus einstellen" + +#: src/slic3r/GUI/DoubleSlider.cpp:1220 +#, possible-c-format +msgid "Jump to height %s\nSet ruler mode\nor Set extruder sequence for the entire print" +msgstr "Auf Höhe %s springen\nLineal-Modus einstellen\noder Extrudersequenz für den gesamten Druck einstellen" + #: src/slic3r/GUI/DoubleSlider.cpp:1075 -#, c-format +#, possible-c-format msgid "Jump to height %s or Set extruder sequence for the entire print" msgstr "Auf Höhe %s wechseln oder Extrudersequenz für den gesamten Druck einstellen" +#: src/slic3r/GUI/DoubleSlider.cpp:1222 +#, possible-c-format +msgid "Jump to height %s or Set ruler mode" +msgstr "Auf Höhe %s springen oder Linealmodus einstellen" + +#: src/slic3r/GUI/DoubleSlider.cpp:1220 +#, possible-c-format +msgid "Jump to height %s Set ruler mode\n or Set extruder sequence for the entire print" +msgstr "Auf Höhe %s springen Lineal-Modus einstellen\n oder Extrudersequenz für den gesamten Druck einstellen" + #: src/slic3r/GUI/DoubleSlider.cpp:1071 src/slic3r/GUI/DoubleSlider.cpp:1852 msgid "Jump to move" msgstr "Zum Bewegen wechseln" @@ -4609,6 +4711,10 @@ msgstr "Länge" msgid "Length of the cooling tube to limit space for cooling moves inside it." msgstr "Länge des Kühlschlauchs, um den Raum für Kühlbewegungen im Inneren zu begrenzen." +#: src/libslic3r/PrintConfig.cpp:1068 +msgid "Length of the infill anchor" +msgstr "Länge des Infill-Ankers" + #. TRN "Slic3r _is licensed under the_ License" #: src/slic3r/GUI/AboutDialog.cpp:141 msgid "License agreements of all following programs (libraries) are part of application license agreement" @@ -4662,6 +4768,14 @@ msgstr "Lädt die Konfiguration aus der angegebenen Datei. Es kann mehr als einm msgid "Load exported configuration file" msgstr "Laden einer exportierten Konfigurationsdatei" +#: src/slic3r/GUI/Plater.cpp:1543 src/slic3r/GUI/Plater.cpp:4976 +msgid "Load File" +msgstr "Datei laden" + +#: src/slic3r/GUI/Plater.cpp:1548 src/slic3r/GUI/Plater.cpp:4981 +msgid "Load Files" +msgstr "Dateien laden" + #: src/slic3r/GUI/GUI_ObjectList.cpp:2038 msgid "Load Part" msgstr "Teil laden" @@ -4757,6 +4871,10 @@ msgstr "Schleifen (minimal)" msgid "Lower Layer" msgstr "Untere Schicht" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:219 +msgid "Lower layer" +msgstr "Untere Schicht" + #: src/slic3r/GUI/Tab.cpp:2346 src/slic3r/GUI/Tab.cpp:2442 #: src/libslic3r/PrintConfig.cpp:1202 src/libslic3r/PrintConfig.cpp:1237 #: src/libslic3r/PrintConfig.cpp:1254 src/libslic3r/PrintConfig.cpp:1271 @@ -4989,6 +5107,10 @@ msgstr "Maximaler Ruck Y" msgid "Maximum jerk Z" msgstr "Maximaler Ruck Z" +#: src/libslic3r/PrintConfig.cpp:1095 +msgid "Maximum length of the infill anchor" +msgstr "Maximale Länge des Infill-Ankers" + #: src/libslic3r/PrintConfig.cpp:2814 msgid "Maximum number of bridges that can be placed on a pillar. Bridges hold support point pinheads and connect to pillars as small branches." msgstr "Maximale Anzahl von Brücken, die auf einen Pfeiler gesetzt werden können. Brücken halten Stützpunkt-Nadelköpfe und verbinden sich als kleine Äste mit den Pfeilern." @@ -5164,7 +5286,7 @@ msgid "Mirror vertically" msgstr "Vertikal spiegeln" #: src/slic3r/Utils/AstroBox.cpp:69 src/slic3r/Utils/OctoPrint.cpp:68 -#, c-format +#, possible-c-format msgid "Mismatched type of print host: %s" msgstr "Nicht übereinstimmender Typ des Druckhosts: %s" @@ -5364,6 +5486,14 @@ msgstr "Mausrad:" msgid "Move" msgstr "Bewegen" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:255 +msgid "Move active slider thumb Left" +msgstr "Bewege aktiven Schieberegler nach links" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:256 +msgid "Move active slider thumb Right" +msgstr "Bewege aktiven Schieberegler nach rechts" + #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1219 msgid "Move clipping plane" msgstr "Beschnittebene bewegen" @@ -5388,6 +5518,16 @@ msgstr "Bewege aktuellen Schieberegler nach oben" msgid "Move drainage hole" msgstr "Drainageloch bewegen" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:209 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:213 +msgid "Move horizontal slider current thumb Left" +msgstr "Horizontalen Schieberegler aktuellen Schieber nach links bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:210 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:214 +msgid "Move horizontal slider current thumb Right" +msgstr "Horizontalen Schieberegler aktuellen Schieber nach rechts bewegen" + #: src/slic3r/GUI/GLCanvas3D.cpp:3810 msgid "Move Object" msgstr "Objekt bewegen" @@ -5416,6 +5556,16 @@ msgstr "Auswahl 10 mm in positiver Y-Richtung verschieben" msgid "Move support point" msgstr "Stützpunkt bewegen" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:208 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:212 +msgid "Move vertical slider current thumb Down" +msgstr "Vertikalen Schieberegler aktuellen Schieber nach unten bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:207 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:211 +msgid "Move vertical slider current thumb Up" +msgstr "Vertikalen Schieberegler aktuellen Schieber nach oben bewegen" + #: src/slic3r/GUI/GCodeViewer.cpp:2492 msgid "Movement" msgstr "Bewegung" @@ -5437,7 +5587,7 @@ msgid "Multi-part object detected" msgstr "Objekt mit mehreren Teilen erkannt" #: src/slic3r/GUI/FirmwareDialog.cpp:419 src/slic3r/GUI/FirmwareDialog.cpp:454 -#, c-format +#, possible-c-format msgid "Multiple %s devices found. Please only connect one at a time for flashing." msgstr "Mehrere %s Geräte gefunden. Bitte immer nur eins zum Flashen anschließen." @@ -5446,14 +5596,8 @@ msgid "Multiple Extruders" msgstr "Mehrere Extruder" #: src/slic3r/GUI/Plater.cpp:2372 -msgid "" -"Multiple objects were loaded for a multi-material printer.\n" -"Instead of considering them as multiple objects, should I consider\n" -"these files to represent a single object having multiple parts?" -msgstr "" -"Für einen Multimaterialdrucker wurden mehrere Objekte geladen.\n" -"Soll ich, anstatt sie als mehrere Objekte zu betrachten, \n" -"diese Dateien als ein einzelnes Objekt mit mehreren Teilen behandeln?" +msgid "Multiple objects were loaded for a multi-material printer.\nInstead of considering them as multiple objects, should I consider\nthese files to represent a single object having multiple parts?" +msgstr "Für einen Multimaterialdrucker wurden mehrere Objekte geladen.\nSoll ich, anstatt sie als mehrere Objekte zu betrachten, \ndiese Dateien als ein einzelnes Objekt mit mehreren Teilen behandeln?" #: src/libslic3r/PrintConfig.cpp:3638 msgid "Multiply copies by creating a grid." @@ -5522,7 +5666,7 @@ msgid "New version is available." msgstr "Eine neue Version ist verfügbar." #: src/slic3r/GUI/UpdateDialogs.cpp:38 -#, c-format +#, possible-c-format msgid "New version of %s is available" msgstr "Eine neue Version von %s ist verfügbar" @@ -5605,14 +5749,8 @@ msgid "Note, that this/those printer(s) will be deleted after deleting of the se msgstr "Beachten Sie, dass dieser/diese Drucker nach dem Löschen der ausgewählten Voreinstellung gelöscht werden." #: src/slic3r/GUI/Tab.cpp:2039 -msgid "" -"Note: All parameters from this group are moved to the Physical Printer settings (see changelog).\n" -"\n" -"A new Physical Printer profile is created by clicking on the \"cog\" icon right of the Printer profiles combo box, by selecting the \"Add physical printer\" item in the Printer combo box. The Physical Printer profile editor opens also when clicking on the \"cog\" icon in the Printer settings tab. The Physical Printer profiles are being stored into PrusaSlicer/physical_printer directory." -msgstr "" -"Hinweis: Alle Parameter aus dieser Gruppe werden in die Einstellungen des physischen Druckers verschoben (siehe Changelog).\n" -"\n" -"Ein neues physisches Druckerprofil wird erstellt, indem man auf das \"Zahnrad\"-Symbol rechts vom Auswahlfeld \"Druckerprofile\" klickt, indem man den Punkt \"Physischen Drucker hinzufügen\" im Auswahlfeld \"Drucker\" auswählt. Der Profil-Editor für physische Drucker wird auch durch Klicken auf das \"Zahnrad\"-Symbol in der Registerkarte \"Druckereinstellungen\" geöffnet. Die Profile des physischen Druckers werden im Verzeichnis PrusaSlicer/physical_printer gespeichert." +msgid "Note: All parameters from this group are moved to the Physical Printer settings (see changelog).\n\nA new Physical Printer profile is created by clicking on the \"cog\" icon right of the Printer profiles combo box, by selecting the \"Add physical printer\" item in the Printer combo box. The Physical Printer profile editor opens also when clicking on the \"cog\" icon in the Printer settings tab. The Physical Printer profiles are being stored into PrusaSlicer/physical_printer directory." +msgstr "Hinweis: Alle Parameter aus dieser Gruppe werden in die Einstellungen des physischen Druckers verschoben (siehe Changelog).\n\nEin neues physisches Druckerprofil wird erstellt, indem man auf das \"Zahnrad\"-Symbol rechts vom Auswahlfeld \"Druckerprofile\" klickt, indem man den Punkt \"Physischen Drucker hinzufügen\" im Auswahlfeld \"Drucker\" auswählt. Der Profil-Editor für physische Drucker wird auch durch Klicken auf das \"Zahnrad\"-Symbol in der Registerkarte \"Druckereinstellungen\" geöffnet. Die Profile des physischen Druckers werden im Verzeichnis PrusaSlicer/physical_printer gespeichert." #: src/slic3r/Utils/AstroBox.cpp:92 msgid "Note: AstroBox version at least 1.1.0 is required." @@ -5801,10 +5939,14 @@ msgid "On OSX there is always only one instance of app running by default. Howev msgstr "Unter OSX wird standardmäßig immer nur eine Instanz der Anwendung ausgeführt. Es ist jedoch erlaubt, mehrere Instanzen derselben Anwendung von der Befehlszeile aus auszuführen. In einem solchen Fall erlauben diese Einstellungen nur eine Instanz." #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:359 -#, c-format +#, possible-c-format msgid "On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain." msgstr "Auf diesem System verwendet %s HTTPS-Zertifikate aus dem System Zertifikatsspeicher oder Schlüsselbund." +#: src/slic3r/GUI/KBShortcutsDialog.cpp:215 +msgid "On/Off one layer mode of the vertical slider" +msgstr "Ein/Aus Einschichtmodus des vertikalen Schiebereglers" + #: src/slic3r/GUI/DoubleSlider.cpp:1064 msgid "One layer mode" msgstr "Eine Schicht Modus" @@ -5815,7 +5957,7 @@ msgstr "Eines oder mehrere Objekte wurden einem Extruder zugewiesen, der auf die #: src/libslic3r/PrintConfig.cpp:2045 src/libslic3r/PrintConfig.cpp:2840 msgid "Only create support if it lies on a build plate. Don't create support on a print." -msgstr "Nur dann Stützen schaffen, wenn sie auf der Druckplattform aufbauen. Erstellt keine Stützstrukturen, die auf dem Ausdruck gründen würden." +msgstr "Nur dann Stützen schaffen, wenn sie auf der Druckplattform aufbauen. Erstellt keine Stützen, die auf dem Ausdruck gründen würden." #: src/libslic3r/PrintConfig.cpp:1071 msgid "Only infill where needed" @@ -5866,6 +6008,10 @@ msgstr "Öffne eine neue PrusaSlicer-Instanz" msgid "Open a project file" msgstr "Öffne eine Projektdatei" +#: src/slic3r/GUI/Plater.cpp:1417 +msgid "Open as project" +msgstr "Als Projekt öffnen" + #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:330 msgid "Open CA certificate file" msgstr "Open CA Zertifikat Datei" @@ -5908,7 +6054,7 @@ msgid "Open PrusaSlicer" msgstr "PrusaSlicer öffnen" #: src/slic3r/GUI/MainFrame.cpp:918 src/slic3r/GUI/MainFrame.cpp:1317 -#, c-format +#, possible-c-format msgid "Open the %s website in your browser" msgstr "%s-Website in Ihrem Browser öffnen" @@ -5943,7 +6089,7 @@ msgstr "Optionen" #: src/slic3r/GUI/Tab.cpp:1460 msgid "Options for support material and raft" -msgstr "Optionen für Stützstrukturen und Raft" +msgstr "Optionen für Stützmaterial und Raft" #: src/slic3r/GUI/Mouse3DController.cpp:315 msgid "Options:" @@ -6210,7 +6356,7 @@ msgid "Physical printers" msgstr "Physische Drucker" #: src/slic3r/GUI/ConfigWizard.cpp:1226 -#, c-format +#, possible-c-format msgid "Pick another vendor supported by %s" msgstr "Wählen Sie einen anderen Hersteller, der von %s unterstützt wird" @@ -6342,6 +6488,10 @@ msgstr "Die \"%1%\" Voreinstellung ist mit dem neuen Druckerprofil nicht kompati msgid "Preset with name \"%1%\" already exists and is imcopatible with selected printer." msgstr "Die Voreinstellung mit dem Namen \"%1%\" existiert bereits und ist mit dem ausgewählten Drucker nicht kompatibel." +#: src/slic3r/GUI/SavePresetDialog.cpp:136 +msgid "Preset with name \"%1%\" already exists and is incopatible with selected printer." +msgstr "Die Voreinstellung mit dem Namen \"%1%\" existiert bereits und ist mit dem ausgewählten Drucker nicht kompatibel." + #: src/slic3r/GUI/SavePresetDialog.cpp:148 msgid "Preset with name \"%1%\" already exists." msgstr "Eine Voreinstellung mit dem Namen \"%1%\" existiert bereits." @@ -6360,20 +6510,14 @@ msgid "Press to activate selection rectangle" msgstr "Drücken um das Auswahlrechteck zu aktivieren" #: src/slic3r/GUI/KBShortcutsDialog.cpp:155 -msgid "" -"Press to select multiple objects\n" -"or move multiple objects with mouse" -msgstr "" -"Drücken zum Auswählen des mehrteiligen Objekts \n" -"oder Bewegen des mehrteiligen Objekts mit der Maus" +msgid "Press to select multiple objects\nor move multiple objects with mouse" +msgstr "Drücken zum Auswählen des mehrteiligen Objekts \noder Bewegen des mehrteiligen Objekts mit der Maus" #: src/slic3r/GUI/KBShortcutsDialog.cpp:221 #: src/slic3r/GUI/KBShortcutsDialog.cpp:222 #: src/slic3r/GUI/KBShortcutsDialog.cpp:231 #: src/slic3r/GUI/KBShortcutsDialog.cpp:232 -msgid "" -"Press to speed up 5 times while moving thumb\n" -"with arrow keys or mouse wheel" +msgid "Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel" msgstr "Drücken, um 5 Mal schneller zu werden, während der Regler mit Pfeiltasten oder Mausrad bewegt wird" #: src/slic3r/GUI/KBShortcutsDialog.cpp:212 src/slic3r/GUI/Plater.cpp:4052 @@ -6529,7 +6673,7 @@ msgstr "Der Druck erfolgt mit mehreren Extrudern mit unterschiedlichen Düsendur #. TRN "Processing input_file_basename" #: src/slic3r/GUI/MainFrame.cpp:1550 -#, c-format +#, possible-c-format msgid "Processing %s" msgstr "Berechne %s" @@ -6584,10 +6728,8 @@ msgid "PrusaSlicer is closing: Unsaved Changes" msgstr "PrusaSlicer wird geschlossen: Nicht gespeicherte Änderungen" #: src/slic3r/GUI/OpenGLManager.cpp:259 -#, c-format -msgid "" -"PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" -"while OpenGL version %s, render %s, vendor %s was detected." +#, possible-c-format +msgid "PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \nwhile OpenGL version %s, render %s, vendor %s was detected." msgstr "PrusaSlicer benötigt einen OpenGL 2.0-fähigen Grafiktreiber, um korrekt zu laufen, während die OpenGL-Version %s, Render %s, Hersteller %s erkannt wurde." #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:50 @@ -6599,14 +6741,8 @@ msgid "PrusaSlicer will remember your action." msgstr "PrusaSlicer wird sich an Ihre Aktion erinnern." #: src/slic3r/GUI/ConfigWizard.cpp:1174 -msgid "" -"PrusaSlicer's user interfaces comes in three variants:\n" -"Simple, Advanced, and Expert.\n" -"The Simple mode shows only the most frequently used settings relevant for regular 3D printing. The other two offer progressively more sophisticated fine-tuning, they are suitable for advanced and expert users, respectively." -msgstr "" -"Die Benutzeroberflächen von PrusaSlicer sind in drei Varianten erhältlich:\n" -"Einfach, Fortgeschritten und Experte.\n" -"Der einfache Modus zeigt nur die am häufigsten verwendeten Einstellungen, die für den regulären 3D-Druck relevant sind. Die beiden anderen bieten eine immer anspruchsvollere Feinabstimmung, sie sind für fortgeschrittene bzw. erfahrene Anwender geeignet." +msgid "PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\nThe Simple mode shows only the most frequently used settings relevant for regular 3D printing. The other two offer progressively more sophisticated fine-tuning, they are suitable for advanced and expert users, respectively." +msgstr "Die Benutzeroberflächen von PrusaSlicer sind in drei Varianten erhältlich:\nEinfach, Fortgeschritten und Experte.\nDer einfache Modus zeigt nur die am häufigsten verwendeten Einstellungen, die für den regulären 3D-Druck relevant sind. Die beiden anderen bieten eine immer anspruchsvollere Feinabstimmung, sie sind für fortgeschrittene bzw. erfahrene Anwender geeignet." #: src/slic3r/GUI/UnsavedChangesDialog.cpp:668 msgid "PrusaSlicer: Don't ask me again" @@ -6652,7 +6788,7 @@ msgstr "Schnell" #: src/slic3r/GUI/GUI_ObjectList.cpp:1661 #: src/slic3r/GUI/GUI_ObjectList.cpp:1667 #: src/slic3r/GUI/GUI_ObjectList.cpp:2008 -#, c-format +#, possible-c-format msgid "Quick Add Settings (%s)" msgstr "Schnelles Einstellen (%s)" @@ -6665,7 +6801,7 @@ msgid "Quick Slice and Save As" msgstr "Quick Slice und Speichern unter" #: src/slic3r/GUI/MainFrame.cpp:1144 src/slic3r/GUI/MainFrame.cpp:1402 -#, c-format +#, possible-c-format msgid "Quit %s" msgstr "%s verlassen" @@ -6690,14 +6826,8 @@ msgid "Ramming customization" msgstr "Einstellungen für das Rammen" #: src/slic3r/GUI/WipeTowerDialog.cpp:41 -msgid "" -"Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n" -"\n" -"This is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." -msgstr "" -"Rammen steht für die beschleunigte Extrusion unmittelbar vor einem Werkzeugwechsel in einem MM-Drucker mit einem Extruder. Der Zweck ist, die Spitze des entladenen Filaments geeignet zu formen, damit es das Laden des neuen Filaments nicht behindert und später selber wieder eingeführt werden kann. Diese Phase ist wichtig und verschiedene Materialien können unterschiedliche Extrusionsgeschwindigkeiten benötigen, um die richtige Form zu erzielen. Aus diesem Grund können die Extrusionsraten für das Rammen angepasst werden.\n" -"\n" -"Dies ist eine Einstellung für fortgeschrittene Benutzer. Falsche Anpassungen werden sehr wahrscheinlich zu Verstopfungen führen oder dazu, dass die Zähne der Extruderwelle ins Filament einschneiden usw." +msgid "Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." +msgstr "Rammen steht für die beschleunigte Extrusion unmittelbar vor einem Werkzeugwechsel in einem MM-Drucker mit einem Extruder. Der Zweck ist, die Spitze des entladenen Filaments geeignet zu formen, damit es das Laden des neuen Filaments nicht behindert und später selber wieder eingeführt werden kann. Diese Phase ist wichtig und verschiedene Materialien können unterschiedliche Extrusionsgeschwindigkeiten benötigen, um die richtige Form zu erzielen. Aus diesem Grund können die Extrusionsraten für das Rammen angepasst werden.\n\nDies ist eine Einstellung für fortgeschrittene Benutzer. Falsche Anpassungen werden sehr wahrscheinlich zu Verstopfungen führen oder dazu, dass die Zähne der Extruderwelle ins Filament einschneiden usw." #: src/slic3r/GUI/WipeTowerDialog.cpp:91 msgid "Ramming line spacing" @@ -6757,7 +6887,7 @@ msgid "Recent projects" msgstr "L&etzte Projekte" #: src/slic3r/GUI/PresetHints.cpp:262 -#, c-format +#, possible-c-format msgid "Recommended object thin wall thickness for layer height %.2f and" msgstr "Empfohlene Stärke der dünnen Wände des Objekts für die Schichthöhe %.2f und" @@ -6792,7 +6922,7 @@ msgid "Redo" msgstr "Redo" #: src/slic3r/GUI/GLCanvas3D.cpp:4382 -#, c-format +#, possible-c-format msgid "Redo %1$d Action" msgid_plural "Redo %1$d Actions" msgstr[0] "Redo %1$d Aktion" @@ -7016,22 +7146,22 @@ msgid "Report an I&ssue" msgstr "Ein &Problem melden" #: src/slic3r/GUI/MainFrame.cpp:928 src/slic3r/GUI/MainFrame.cpp:1327 -#, c-format +#, possible-c-format msgid "Report an issue on %s" msgstr "Einen Problem melden über %s" #: src/slic3r/Utils/PresetUpdater.cpp:733 -#, c-format +#, possible-c-format msgid "requires max. %s" msgstr "benötigt max. %s" #: src/slic3r/Utils/PresetUpdater.cpp:730 -#, c-format +#, possible-c-format msgid "requires min. %s" msgstr "benötigt min. %s" #: src/slic3r/Utils/PresetUpdater.cpp:726 -#, c-format +#, possible-c-format msgid "requires min. %s and max. %s" msgstr "benötigt min. %s und max. %s" @@ -7215,8 +7345,12 @@ msgstr "Rotationswinkel um die Y-Achse in Grad." msgid "Rotation angle around the Z axis in degrees." msgstr "Rotationswinkel um die Z-Achse in Grad." +#: src/slic3r/GUI/DoubleSlider.cpp:1836 +msgid "Ruler mode" +msgstr "Lineal-Modus" + #: src/slic3r/GUI/GUI_App.cpp:1474 -#, c-format +#, possible-c-format msgid "Run %s" msgstr "%s ausführen" @@ -7248,12 +7382,12 @@ msgid "Save" msgstr "Speichern" #: src/slic3r/GUI/SavePresetDialog.cpp:72 -#, c-format +#, possible-c-format msgid "Save %s as:" msgstr "Speichere %s als:" #: src/slic3r/GUI/MainFrame.cpp:1527 -#, c-format +#, possible-c-format msgid "Save %s file as:" msgstr "Speichere %s Datei als:" @@ -7275,7 +7409,7 @@ msgstr "Sichert die Konfiguration in der angegebenen Datei." #. TRN "Save current Settings" #: src/slic3r/GUI/Tab.cpp:203 -#, c-format +#, possible-c-format msgid "Save current %s" msgstr "Speichere aktuelle %s" @@ -7451,6 +7585,10 @@ msgstr "Alle Punkte auswählen" msgid "Select all standard printers" msgstr "Wähle alle Standarddrucker" +#: src/slic3r/GUI/Plater.cpp:1422 +msgid "Select an action to apply to the file" +msgstr "Wählen Sie eine Aktion, die auf die Datei angewendet werden soll" + #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1215 msgid "Select by rectangle" msgstr "Auswahl über Rechteck" @@ -7520,14 +7658,8 @@ msgid "Select what kind of support do you need" msgstr "Wählen Sie aus, welche Art von Unterstützung Sie benötigen" #: src/slic3r/GUI/DoubleSlider.cpp:2135 -msgid "" -"Select YES if you want to delete all saved tool changes, \n" -"NO if you want all tool changes switch to color changes, \n" -"or CANCEL to leave it unchanged." -msgstr "" -"Wählen Sie JA, wenn Sie alle gespeicherten Werkzeugänderungen löschen möchten, \n" -"NEIN, wenn Sie möchten, dass alle Werkzeugänderungen auf Farbwechsel umgestellt werden, \n" -"oder ABBRECHEN, um sie unverändert zu lassen." +msgid "Select YES if you want to delete all saved tool changes, \nNO if you want all tool changes switch to color changes, \nor CANCEL to leave it unchanged." +msgstr "Wählen Sie JA, wenn Sie alle gespeicherten Werkzeugänderungen löschen möchten, \nNEIN, wenn Sie möchten, dass alle Werkzeugänderungen auf Farbwechsel umgestellt werden, \noder ABBRECHEN, um sie unverändert zu lassen." #: src/slic3r/GUI/Selection.cpp:191 msgid "Selection-Add" @@ -7667,7 +7799,7 @@ msgid "Set number of instances" msgstr "Setze Anzahl der Kopien" #: src/slic3r/GUI/Plater.cpp:4860 -#, c-format +#, possible-c-format msgid "Set numbers of copies to %d" msgstr "Setze Anzahl der Kopien auf %d" @@ -7687,6 +7819,10 @@ msgstr "Setze Druckbar" msgid "Set Printable Instance" msgstr "Setze druckbare Kopie" +#: src/slic3r/GUI/DoubleSlider.cpp:1836 +msgid "Set ruler mode" +msgstr "Lineal-Modus einstellen" + #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:893 msgid "Set Scale" msgstr "Setze Skalierung" @@ -7729,7 +7865,7 @@ msgstr "Setzen Sie dies auf einen Nicht-Nullwert, um eine manuelle Extrusionsbre #: src/libslic3r/PrintConfig.cpp:2090 msgid "Set this to a non-zero value to set a manual extrusion width for support material. If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. If expressed as percentage (for example 90%) it will be computed over layer height." -msgstr "Setzen Sie dies auf einen Nicht-Nullwert, um eine manuelle Extrusionsbreite für die Stützstrukturen anzugeben. Falls auf null belassen, wird die Standard-Extrusionsbreite verwendet (falls angeben), ansonsten wird der Durchmesser der Druckdüse verwendet. Als Prozentwert (z.B. 90%) angegeben, wird dieser ausgehend von der Schichthöhe berechnet." +msgstr "Setzen Sie dies auf einen Nicht-Nullwert, um eine manuelle Extrusionsbreite für das Stützmaterial anzugeben. Falls auf null belassen, wird die Standard-Extrusionsbreite verwendet (falls angeben), ansonsten wird der Durchmesser der Druckdüse verwendet. Als Prozentwert (z.B. 90%) angegeben, wird dieser ausgehend von der Schichthöhe berechnet." #: src/libslic3r/PrintConfig.cpp:548 msgid "Set this to the clearance radius around your extruder. If the extruder is not centered, choose the largest value for safety. This setting is used to check for collisions and to display the graphical preview in the plater." @@ -7756,12 +7892,8 @@ msgid "Set upper thumb to current slider thumb" msgstr "Stelle den oberen Regler auf den aktuellen Schieberegler" #: src/libslic3r/PrintConfig.cpp:3714 -msgid "" -"Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\n" -"For example. loglevel=2 logs fatal, error and warning level messages." -msgstr "" -"Stellt die Empfindlichkeit der Protokollierung ein. 0:fatal, 1:Fehler, 2:Warnung, 3:Info, 4:Debug, 5: Trace.\n" -"Zum Beispiel. loglevel=2 protokolliert fatale, Fehler- und Warnstufenmeldungen." +msgid "Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\nFor example. loglevel=2 logs fatal, error and warning level messages." +msgstr "Stellt die Empfindlichkeit der Protokollierung ein. 0:fatal, 1:Fehler, 2:Warnung, 3:Info, 4:Debug, 5: Trace.\nZum Beispiel. loglevel=2 protokolliert fatale, Fehler- und Warnstufenmeldungen." #: src/slic3r/GUI/BedShapeDialog.cpp:292 src/slic3r/GUI/MainFrame.cpp:1969 msgid "Settings" @@ -7838,10 +7970,22 @@ msgstr "\"Über\"-Dialog anzeigen" msgid "Show advanced settings" msgstr "Ausführliche Einstellungen anzeigen" +#: src/slic3r/GUI/Preferences.cpp:120 +msgid "Show drop project dialog" +msgstr "Projekt-Drop-Dialog anzeigen" + #: src/slic3r/GUI/PrintHostDialogs.cpp:157 msgid "Show error message" msgstr "Fehlermeldungen anzeigen" +#: src/slic3r/GUI/DoubleSlider.cpp:1832 +msgid "Show estimated print time" +msgstr "Geschätzte Druckzeit anzeigen" + +#: src/slic3r/GUI/DoubleSlider.cpp:1832 +msgid "Show estimated print time on the ruler" +msgstr "Geschätzte Druckzeit auf dem Lineal anzeigen" + #: src/slic3r/GUI/Preferences.cpp:112 msgid "Show incompatible print and filament presets" msgstr "Inkompatible Druck- und Filamenteinstellungen anzeigen" @@ -7854,6 +7998,14 @@ msgstr "Liste der Tastaturkürzel anzeigen" msgid "Show normal mode" msgstr "Normalen Modus zeigen" +#: src/slic3r/GUI/DoubleSlider.cpp:1828 +msgid "Show object height" +msgstr "Objekthöhe anzeigen" + +#: src/slic3r/GUI/DoubleSlider.cpp:1828 +msgid "Show object height on the ruler" +msgstr "Objekthöhe auf dem Lineal anzeigen" + #: src/slic3r/GUI/MainFrame.cpp:1294 msgid "Show object/instance labels in 3D scene" msgstr "Objekt-/Kopiebeschriftungen in der 3D-Szene anzeigen" @@ -7930,10 +8082,18 @@ msgstr "Zeige Benutzerkonfigurationsordner (datadir)" msgid "Show/Hide 3Dconnexion devices settings dialog" msgstr "Einstellungsdialog für 3Dconnexion-Geräte ein-/ausblenden" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:175 +msgid "Show/Hide 3Dconnexion devices settings dialog, if enabled" +msgstr "Dialogfeld Einstellungen für 3Dconnexion-Geräte anzeigen/verbergen, falls aktiviert" + #: src/slic3r/GUI/KBShortcutsDialog.cpp:216 msgid "Show/Hide Legend & Estimated printing time" msgstr "Legende & geschätzte Druckzeit anzeigen/verbergen" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:216 +msgid "Show/Hide Legend and Estimated printing time" +msgstr "Legende und geschätzte Druckzeit anzeigen/verbergen" + #: src/slic3r/GUI/KBShortcutsDialog.cpp:141 msgid "Show/Hide object/instance labels" msgstr "Objekt-/Kopiebeschriftungen ein-/ausblenden" @@ -7959,14 +8119,8 @@ msgid "Single Extruder Multi Material" msgstr "Einzelextruder mit Multi-Material" #: src/slic3r/GUI/Tab.cpp:2101 -msgid "" -"Single Extruder Multi Material is selected, \n" -"and all extruders must have the same diameter.\n" -"Do you want to change the diameter for all extruders to first extruder nozzle diameter value?" -msgstr "" -"Einzel-Extruder Multi-Material ist ausgewählt, \n" -"und alle Extruder müssen den gleichen Durchmesser haben.\n" -"Möchten Sie den Durchmesser für alle Extruder auf den Wert des ersten Extruderdüsendurchmessers ändern?" +msgid "Single Extruder Multi Material is selected, \nand all extruders must have the same diameter.\nDo you want to change the diameter for all extruders to first extruder nozzle diameter value?" +msgstr "Einzel-Extruder Multi-Material ist ausgewählt, \nund alle Extruder müssen den gleichen Durchmesser haben.\nMöchten Sie den Durchmesser für alle Extruder auf den Wert des ersten Extruderdüsendurchmessers ändern?" #: src/slic3r/GUI/Tab.cpp:2476 msgid "Single extruder multimaterial parameters" @@ -8075,6 +8229,10 @@ msgstr "PrusaSlicer kann G-Code Dateien zu einem Druckerhost hochladen. Dieses F msgid "Slic3r can upload G-code files to a printer host. This field should contain the hostname, IP address or URL of the printer host instance." msgstr "PrusaSlicer kann G-Code Dateien auf einen Drucker-Host hochladen. Dieses Feld sollte den Hostnamen, die IP-Adresse oder die URL der Drucker-Hostinstanz enthalten." +#: src/libslic3r/PrintConfig.cpp:100 +msgid "Slic3r can upload G-code files to a printer host. This field should contain the hostname, IP address or URL of the printer host instance. Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL in the following format: https://username:password@your-octopi-address/" +msgstr "PrusaSlicer kann G-Code-Dateien auf einen Drucker-Host hochladen. Dieses Feld sollte den Hostnamen, die IP-Adresse oder die URL der Drucker-Host-Instanz enthalten. Auf einen Drucker-Host hinter HAProxy mit aktivierter Basisauthentifizierung kann zugegriffen werden, indem der Benutzername und das Passwort im folgenden Format in die URL eingegeben werden: https://username:password@Ihre-octopi-addresse/" + #: src/libslic3r/PrintConfig.cpp:1407 msgid "Slic3r will not scale speed down below this speed." msgstr "PrusaSlicer wird die Geschwindigkeit nicht unterhalb dieser Geschwindigkeit skalieren." @@ -8149,7 +8307,7 @@ msgstr "Slice das Modell" #: src/libslic3r/SLAPrintSteps.cpp:50 msgid "Slicing supports" -msgstr "Slice Stützstrukturen" +msgstr "Slice Stützen" #: src/libslic3r/PrintConfig.cpp:2557 msgid "Slow" @@ -8219,7 +8377,7 @@ msgstr "Lösliches Material" #: src/libslic3r/PrintConfig.cpp:791 msgid "Soluble material is most likely used for a soluble support." -msgstr "Lösliches Material wird meistens für Stützstrukturen verwendet." +msgstr "Lösliches Material wird meistens für lösliche Stützen verwendet." #: src/slic3r/GUI/UnsavedChangesDialog.cpp:735 msgid "Some fields are too long to fit. Right mouse click reveals the full text." @@ -8230,13 +8388,9 @@ msgid "Some G/M-code commands, including temperature control and others, are not msgstr "Einige G/M-Code Befehle, einschließlich Temperaturregelung und andere, sind nicht universell einsetzbar. Stellen Sie diese Option auf die Firmware Ihres Druckers ein, um eine kompatible Ausgabe zu erhalten. Der Zusatz \"No Extrusion\" verhindert, dass PrusaSlicer überhaupt einen Extrusionswert exportiert." #: src/slic3r/GUI/Plater.cpp:2309 -#, c-format -msgid "" -"Some object(s) in file %s looks like saved in inches.\n" -"Should I consider them as a saved in inches and convert them?" -msgstr "" -"Einige Objekte in der Datei %s sehen aus wie in Zoll gespeichert.\n" -"Als in Zoll gespeicherte Objekte betrachten und konvertieren?" +#, possible-c-format +msgid "Some object(s) in file %s looks like saved in inches.\nShould I consider them as a saved in inches and convert them?" +msgstr "Einige Objekte in der Datei %s sehen aus wie in Zoll gespeichert.\nAls in Zoll gespeicherte Objekte betrachten und konvertieren?" #: src/slic3r/GUI/GLCanvas3D.cpp:636 msgid "Some objects are not visible." @@ -8258,6 +8412,10 @@ msgstr "Einige Objekte können mit ein paar kleineren Grundschichten auskommen, msgid "Some printers or printer setups may have difficulties printing with a variable layer height. Enabled by default." msgstr "Mit einigen Druckern oder Druckerkonfigurationen ist es schwierig, mit einer variablen Schichthöhe zu drucken. Standardmässig aktiviert." +#: src/slic3r/GUI/GLCanvas3D.cpp:3967 +msgid "Spacing" +msgstr "Abstand" + #: src/libslic3r/PrintConfig.cpp:2126 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "Abstand zwischen den Schnittstellenlinien. Auf null stellen, um ein massives Interface zu erhalten." @@ -8268,7 +8426,7 @@ msgstr "Abstand zwischen Bügelwegen" #: src/libslic3r/PrintConfig.cpp:2160 msgid "Spacing between support material lines." -msgstr "Abstand zwischen Stützstrukturlinien." +msgstr "Abstand zwischen Stützmateriallinien." #: src/slic3r/GUI/GUI_ObjectList.cpp:96 src/slic3r/GUI/GUI_ObjectList.cpp:655 #: src/slic3r/GUI/GUI_Preview.cpp:278 src/slic3r/GUI/Tab.cpp:1474 @@ -8314,7 +8472,7 @@ msgstr "Geschwindigkeit für den Druck von Trägermaterial-Schnittstellenschicht #: src/libslic3r/PrintConfig.cpp:2169 msgid "Speed for printing support material." -msgstr "Druckgeschwindigkeit der Stützstrukturen." +msgstr "Druckgeschwindigkeit des Stützmaterials." #: src/libslic3r/PrintConfig.cpp:1093 msgid "Speed for printing the internal fill. Set to zero for auto." @@ -8403,6 +8561,10 @@ msgstr "In Teile trennen" msgid "Split to Parts" msgstr "In Teile trennen" +#: src/libslic3r/PrintConfig.cpp:812 +msgid "Spool weight" +msgstr "Gewicht der Spule" + #: src/slic3r/GUI/ConfigWizard.cpp:307 msgid "Standard" msgstr "Standard" @@ -8437,30 +8599,8 @@ msgid "Start the application" msgstr "Anwendung starten" #: src/slic3r/GUI/GUI_App.cpp:386 -msgid "" -"Starting with %1% 2.3, configuration directory on Linux has changed (according to XDG Base Directory Specification) to \n" -"%2%.\n" -"\n" -"This directory did not exist yet (maybe you run the new version for the first time).\n" -"However, an old %1% configuration directory was detected in \n" -"%3%.\n" -"\n" -"Consider moving the contents of the old directory to the new location in order to access your profiles, etc.\n" -"Note that if you decide to downgrade %1% in future, it will use the old location again.\n" -"\n" -"What do you want to do now?" -msgstr "" -"Beginnend mit %1% 2.3 hat sich das Konfigurationsverzeichnis unter Linux (gemäß XDG Base Directory Specification) geändert in \n" -"%2%.\n" -"\n" -"Dieses Verzeichnis existierte noch nicht (vielleicht führen Sie die neue Version zum ersten Mal aus).\n" -"Es wurde jedoch ein altes %1%-Konfigurationsverzeichnis entdeckt in \n" -"%3%.\n" -"\n" -"Ziehen Sie in Betracht, den Inhalt des alten Verzeichnisses an den neuen Ort zu verschieben, um auf Ihre Profile usw. zuzugreifen.\n" -"Beachten Sie, dass bei einem zukünftigen Downgrade von %1% wieder der alte Speicherort verwendet wird.\n" -"\n" -"Was möchten Sie jetzt tun?" +msgid "Starting with %1% 2.3, configuration directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\nThis directory did not exist yet (maybe you run the new version for the first time).\nHowever, an old %1% configuration directory was detected in \n%3%.\n\nConsider moving the contents of the old directory to the new location in order to access your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old location again.\n\nWhat do you want to do now?" +msgstr "Beginnend mit %1% 2.3 hat sich das Konfigurationsverzeichnis unter Linux (gemäß XDG Base Directory Specification) geändert in \n%2%.\n\nDieses Verzeichnis existierte noch nicht (vielleicht führen Sie die neue Version zum ersten Mal aus).\nEs wurde jedoch ein altes %1%-Konfigurationsverzeichnis entdeckt in \n%3%.\n\nZiehen Sie in Betracht, den Inhalt des alten Verzeichnisses an den neuen Ort zu verschieben, um auf Ihre Profile usw. zuzugreifen.\nBeachten Sie, dass bei einem zukünftigen Downgrade von %1% wieder der alte Speicherort verwendet wird.\n\nWas möchten Sie jetzt tun?" #: src/slic3r/GUI/PrintHostDialogs.cpp:149 msgid "Status" @@ -8483,7 +8623,7 @@ msgid "Stealth mode" msgstr "Stealth Modus" #: src/slic3r/GUI/Plater.cpp:5118 -#, c-format +#, possible-c-format msgid "STL file exported to %s" msgstr "Die STL-Datei wurde exportiert zu %s" @@ -8496,7 +8636,7 @@ msgid "Success!" msgstr "Erfolg!" #: src/slic3r/GUI/Plater.cpp:2047 -#, c-format +#, possible-c-format msgid "Successfully unmounted. The device %s(%s) can now be safely removed from the computer." msgstr "Erfolgreich ausgeworfen. Das Gerät %s(%s) kann nun sicher vom Computer entfernt werden." @@ -8556,29 +8696,29 @@ msgstr "Schnittstelle zu den Stützen" #: src/libslic3r/PrintConfig.cpp:2177 src/libslic3r/PrintConfig.cpp:2185 #: src/libslic3r/PrintConfig.cpp:2199 msgid "Support material" -msgstr "Stützstrukturen" +msgstr "Stützmaterial" #: src/slic3r/GUI/GUI_Preview.cpp:312 src/libslic3r/ExtrusionEntity.cpp:325 #: src/libslic3r/ExtrusionEntity.cpp:358 src/libslic3r/PrintConfig.cpp:2133 msgid "Support material interface" -msgstr "Schnittstellenmaterial zu den Stützstrukturen" +msgstr "Schnittstellenmaterial zum Stützmaterial" #: src/libslic3r/PrintConfig.cpp:2186 msgid "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended)." -msgstr "Für Überhänge, deren Neigungswinkel (90° = vertikal) oberhalb der vorgegebenen Schwelle liegt, wird keine Stützstruktur erzeugt. Mit anderen Worten, dieser Wert stellt die größte horizontale Steigung (gemessen von der horizontalen Ebene) dar, die Sie ohne Trägermaterial drucken können. Für die automatische Erkennung auf null setzen (empfohlen)." +msgstr "Für Überhänge, deren Neigungswinkel (90° = vertikal) oberhalb der vorgegebenen Schwelle liegt, wird kein Stützmaterial erzeugt. Mit anderen Worten, dieser Wert stellt die größte horizontale Steigung (gemessen von der horizontalen Ebene) dar, die Sie ohne Stützmaterial drucken können. Für die automatische Erkennung auf null setzen (empfohlen)." #: src/libslic3r/PrintConfig.cpp:2106 msgid "Support material/raft interface extruder" -msgstr "Stützstrukturen/Raft Schnittstellen Extruder" +msgstr "Stützmaterial/Raft Schnittstellen Extruder" #: src/libslic3r/PrintConfig.cpp:2079 msgid "Support material/raft/skirt extruder" -msgstr "Stützstrukturen/Raft/Schürzen Extruder" +msgstr "Stützmaterial/Raft/Schürzen Extruder" #: src/slic3r/GUI/Plater.cpp:366 src/libslic3r/PrintConfig.cpp:2043 #: src/libslic3r/PrintConfig.cpp:2838 msgid "Support on build plate only" -msgstr "Stützstrukturen nur auf dem Druckbett" +msgstr "Stützen nur auf dem Druckbett" #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:762 msgid "Support parameter change" @@ -8624,12 +8764,12 @@ msgid "Supports stealth mode" msgstr "Unterstützt Stealth Modus" #: src/slic3r/GUI/ConfigManipulation.cpp:158 -msgid "" -"Supports work better, if the following feature is enabled:\n" -"- Detect bridging perimeters" -msgstr "" -"Stützen funktionieren besser, wenn die folgende Funktion aktiviert ist:\n" -"- Erkennen von Umfangbrücken" +msgid "Supports work better, if the following feature is enabled:\n- Detect bridging perimeters" +msgstr "Stützen funktionieren besser, wenn die folgende Funktion aktiviert ist:\n- Erkennen von Umfangbrücken" + +#: src/slic3r/GUI/DoubleSlider.cpp:1824 +msgid "Supprese show the ruler" +msgstr "LIneal zeigen unterdrücken" #: src/slic3r/GUI/Preferences.cpp:104 msgid "Suppress \" - default - \" presets" @@ -8639,6 +8779,10 @@ msgstr "\"Standard\"-Einstellungen unterdrücken" msgid "Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available." msgstr "\"Standard\"-Einstellungen in den Auswahlen für Druck / Filament / Drucker unterdrücken, falls andere gültige Voreinstellungen vorhanden sind." +#: src/slic3r/GUI/Preferences.cpp:276 +msgid "Suppress to open hyperlink in browser" +msgstr "Öffnen des Hyperlinks im Browser unterdrücken" + #: src/slic3r/GUI/MainFrame.cpp:1527 msgid "SVG" msgstr "SVG" @@ -8676,7 +8820,7 @@ msgid "Switch to Settings" msgstr "Zu Einstellungen wechseln" #: src/slic3r/GUI/wxExtensions.cpp:623 -#, c-format +#, possible-c-format msgid "Switch to the %s mode" msgstr "Wechseln zum %s Modus" @@ -8685,22 +8829,12 @@ msgid "Switching Presets: Unsaved Changes" msgstr "Voreinstellungen umschalten: Nicht gespeicherte Änderungen" #: src/slic3r/GUI/GUI_App.cpp:1608 -msgid "" -"Switching the language will trigger application restart.\n" -"You will lose content of the plater." -msgstr "" -"Das Umschalten der Sprache löst einen Neustart der Anwendung aus.\n" -"Sie verlieren den Inhalt der Druckplatte." +msgid "Switching the language will trigger application restart.\nYou will lose content of the plater." +msgstr "Das Umschalten der Sprache löst einen Neustart der Anwendung aus.\nSie verlieren den Inhalt der Druckplatte." #: src/slic3r/GUI/WipeTowerDialog.cpp:365 -msgid "" -"Switching to simple settings will discard changes done in the advanced mode!\n" -"\n" -"Do you want to proceed?" -msgstr "" -"Das Umschalten auf einfache Einstellungen verwirft die im erweiterten Modus vorgenommenen Änderungen!\n" -"\n" -"Wollen Sie fortfahren?" +msgid "Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?" +msgstr "Das Umschalten auf einfache Einstellungen verwirft die im erweiterten Modus vorgenommenen Änderungen!\n\nWollen Sie fortfahren?" #: src/slic3r/GUI/Tab.cpp:1332 msgid "symbolic profile name" @@ -8772,32 +8906,22 @@ msgid "The %1% infill pattern is not supposed to work at 100%% density." msgstr "Das %1% Füllmuster ist nicht für die Arbeit mit 100%% Dichte vorgesehen." #: src/slic3r/GUI/FirmwareDialog.cpp:548 -#, c-format +#, possible-c-format msgid "The %s device could not have been found" msgstr "Das %s-Gerät konnte nicht gefunden werden" #: src/slic3r/GUI/FirmwareDialog.cpp:436 -#, c-format -msgid "" -"The %s device was not found.\n" -"If the device is connected, please press the Reset button next to the USB connector ..." -msgstr "" -"Das %s-Gerät wurde nicht gefunden.\n" -"Wenn das Gerät angeschlossen ist, drücken Sie bitte die Reset-Taste neben dem USB-Anschluss...." +#, possible-c-format +msgid "The %s device was not found.\nIf the device is connected, please press the Reset button next to the USB connector ..." +msgstr "Das %s-Gerät wurde nicht gefunden.\nWenn das Gerät angeschlossen ist, drücken Sie bitte die Reset-Taste neben dem USB-Anschluss...." #: src/slic3r/GUI/Tab.cpp:1238 msgid "The current custom preset will be detached from the parent system preset." msgstr "Die aktuelle benutzerdefinierte Voreinstellung wird von der Voreinstellung des übergeordneten Systems gelöst." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:925 -msgid "" -"The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" -"Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n" -"once the rotation is embedded into the object coordinates." -msgstr "" -"Das aktuell manipulierte Objekt wird gekippt (Drehwinkel sind keine Vielfachen von 90°).\n" -"Eine ungleiche Skalierung von geschwenkten Objekten ist nur im Weltkoordinatensystem möglich,\n" -"sobald die Drehung in die Objektkoordinaten eingearbeitet wurde." +msgid "The currently manipulated object is tilted (rotation angles are not multiples of 90°).\nNon-uniform scaling of tilted objects is only possible in the World coordinate system,\nonce the rotation is embedded into the object coordinates." +msgstr "Das aktuell manipulierte Objekt wird gekippt (Drehwinkel sind keine Vielfachen von 90°).\nEine ungleiche Skalierung von geschwenkten Objekten ist nur im Weltkoordinatensystem möglich,\nsobald die Drehung in die Objektkoordinaten eingearbeitet wurde." #: src/libslic3r/PrintConfig.cpp:2890 msgid "The default angle for connecting support sticks and junctions." @@ -8825,11 +8949,11 @@ msgstr "Der Extruder der beim Drucken von massivem Infill benutzt werden soll." #: src/libslic3r/PrintConfig.cpp:2108 msgid "The extruder to use when printing support material interface (1+, 0 to use the current extruder to minimize tool changes). This affects raft too." -msgstr "Der Extruder, der für den Druck von Schnittstellen zu den Stützstrukturen verwendet wird (1+, oder null um den aktuellen Extruder für die Minimierung von Werkzeugwechseln zu verwenden). Dies betrifft auch den Raft." +msgstr "Der Extruder, der für den Druck von Schnittstellen zum Stützmaterial verwendet wird (1+, oder null um den aktuellen Extruder für die Minimierung von Werkzeugwechseln zu verwenden). Dies betrifft auch den Raft." #: src/libslic3r/PrintConfig.cpp:2081 msgid "The extruder to use when printing support material, raft and skirt (1+, 0 to use the current extruder to minimize tool changes)." -msgstr "Der Extruder, der für den Druck von Stützstrukturen, Raft und Schürze verwendet wird (1+, oder null um den aktuellen Extruder für die Minimierung von Werkzeugwechseln zu verwenden)." +msgstr "Der Extruder, der für den Druck von Stützmaterial, Raft und Schürze verwendet wird (1+, oder null um den aktuellen Extruder für die Minimierung von Werkzeugwechseln zu verwenden)." #: src/libslic3r/PrintConfig.cpp:763 msgid "The filament material type for use in custom G-codes." @@ -8930,14 +9054,8 @@ msgid "The percentage of smaller pillars compared to the normal pillar diameter msgstr "Der Prozentsatz der im Vergleich zum normalen Pfeilerdurchmesser kleineren Pfeiler, die in problematischen Bereichen eingesetzt werden, in die ein normaler Pfeiler nicht passt." #: src/libslic3r/PrintConfig.cpp:2567 -msgid "" -"The percentage of the bed area. \n" -"If the print area exceeds the specified value, \n" -"then a slow tilt will be used, otherwise - a fast tilt" -msgstr "" -"Der Prozentsatz der Druckbettfläche.\n" -"Wenn der Druckbereich den angegebenen Wert überschreitet,\n" -"wird eine langsame Verkippung verwendet, andernfalls - eine schnelle Verkippung" +msgid "The percentage of the bed area. \nIf the print area exceeds the specified value, \nthen a slow tilt will be used, otherwise - a fast tilt" +msgstr "Der Prozentsatz der Druckbettfläche.\nWenn der Druckbereich den angegebenen Wert überschreitet,\nwird eine langsame Verkippung verwendet, andernfalls - eine schnelle Verkippung" #: src/slic3r/GUI/Tab.cpp:3430 msgid "The physical printer(s) below is based on the preset, you are going to delete." @@ -8984,22 +9102,12 @@ msgid "The selected object couldn't be split because it contains only one part." msgstr "Das ausgewählte Objekt konnte nicht getrennt werden, da es nur aus einem Teil besteht." #: src/slic3r/GUI/MainFrame.cpp:1003 -msgid "" -"The selected project is no longer available.\n" -"Do you want to remove it from the recent projects list?" -msgstr "" -"Das ausgewählte Projekt ist nicht mehr verfügbar.\n" -"Wollen Sie es aus der Liste der letzten Projekte entfernen?" +msgid "The selected project is no longer available.\nDo you want to remove it from the recent projects list?" +msgstr "Das ausgewählte Projekt ist nicht mehr verfügbar.\nWollen Sie es aus der Liste der letzten Projekte entfernen?" #: src/slic3r/GUI/DoubleSlider.cpp:1121 -msgid "" -"The sequential print is on.\n" -"It's impossible to apply any custom G-code for objects printing sequentually.\n" -"This code won't be processed during G-code generation." -msgstr "" -"Der sequentielle Druck ist eingeschaltet.\n" -"Es ist unmöglich, einen benutzerdefinierten G-Code für Objekte anzuwenden, die sequentiell gedruckt werden.\n" -"Dieser Code wird bei der G-Code-Generierung nicht verarbeitet." +msgid "The sequential print is on.\nIt's impossible to apply any custom G-code for objects printing sequentually.\nThis code won't be processed during G-code generation." +msgstr "Der sequentielle Druck ist eingeschaltet.\nEs ist unmöglich, einen benutzerdefinierten G-Code für Objekte anzuwenden, die sequentiell gedruckt werden.\nDieser Code wird bei der G-Code-Generierung nicht verarbeitet." #: src/slic3r/GUI/ConfigWizard.cpp:1187 msgid "The size of the object can be specified in inches" @@ -9018,23 +9126,9 @@ msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "Die Einzugsgeschwindigkeit (sie betrifft nur den Extruderantrieb)." #: src/slic3r/GUI/ConfigManipulation.cpp:80 -#, c-format -msgid "" -"The Spiral Vase mode requires:\n" -"- one perimeter\n" -"- no top solid layers\n" -"- 0% fill density\n" -"- no support material\n" -"- Ensure vertical shell thickness enabled\n" -"- Detect thin walls disabled" -msgstr "" -"Der Spiralvasenmodus erfordert:\n" -"- einen Perimeter\n" -"- keine oberen massiven Schichten\n" -"- 0% Fülldichte\n" -"- kein Stützmaterial\n" -"- Vertikale Schalenstärke sicherstellen aktiv\n" -"- Dünne Wände erkennen nicht aktiv" +#, possible-c-format +msgid "The Spiral Vase mode requires:\n- one perimeter\n- no top solid layers\n- 0% fill density\n- no support material\n- Ensure vertical shell thickness enabled\n- Detect thin walls disabled" +msgstr "Der Spiralvasenmodus erfordert:\n- einen Perimeter\n- keine oberen massiven Schichten\n- 0% Fülldichte\n- kein Stützmaterial\n- Vertikale Schalenstärke sicherstellen aktiv\n- Dünne Wände erkennen nicht aktiv" #: src/libslic3r/Print.cpp:1263 msgid "The Spiral Vase option can only be used when printing a single object." @@ -9072,27 +9166,16 @@ msgid "The vertical distance between object and support material interface. Sett msgstr "Der vertikale Abstand zwischen Objekt und Trägermaterialschnittstelle. Wenn Sie diesen Wert auf 0 setzen, wird PrusaSlicer auch verhindern, dass Bridge-Flow und -Geschwindigkeit für die erste Objektschicht verwendet werden." #: src/slic3r/GUI/Tab.cpp:2731 -msgid "" -"The Wipe option is not available when using the Firmware Retraction mode.\n" -"\n" -"Shall I disable it in order to enable Firmware Retraction?" -msgstr "" -"Die Reinigungsoption ist nicht verfügbar, wenn der Firmware-Einzug verwendet wird.\n" -"\n" -"Soll ich sie ausschalten, um den Firmware-Einzug zu aktivieren?" +msgid "The Wipe option is not available when using the Firmware Retraction mode.\n\nShall I disable it in order to enable Firmware Retraction?" +msgstr "Die Reinigungsoption ist nicht verfügbar, wenn der Firmware-Einzug verwendet wird.\n\nSoll ich sie ausschalten, um den Firmware-Einzug zu aktivieren?" #: src/libslic3r/Print.cpp:1294 msgid "The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)." msgstr "Der Reinigungsturm unterstützt derzeit kein volumetrisches E (use_volumetric_e=0)." #: src/slic3r/GUI/ConfigManipulation.cpp:114 -msgid "" -"The Wipe Tower currently supports the non-soluble supports only\n" -"if they are printed with the current extruder without triggering a tool change.\n" -"(both support_material_extruder and support_material_interface_extruder need to be set to 0)." -msgstr "" -"Der Reinigungsturm unterstützt derzeit nur die unlöslichen Stützen, wenn sie mit dem aktuellen Extruder gedruckt werden, ohne einen Werkzeugwechsel auszulösen.\n" -"(sowohl der Stützstruktur-Extruder als auch der Stützstruktur-Schnittstellen-Extruder müssen auf 0 eingestellt sein)" +msgid "The Wipe Tower currently supports the non-soluble supports only\nif they are printed with the current extruder without triggering a tool change.\n(both support_material_extruder and support_material_interface_extruder need to be set to 0)." +msgstr "Der Reinigungsturm unterstützt derzeit nur die unlöslichen Stützen, wenn sie mit dem aktuellen Extruder gedruckt werden, ohne einen Werkzeugwechsel auszulösen.\n(sowohl der Stützmaterial-Extruder als auch der Stützmaterial-Schnittstellen-Extruder müssen auf 0 eingestellt sein)" #: src/libslic3r/Print.cpp:1426 msgid "The Wipe Tower currently supports the non-soluble supports only if they are printed with the current extruder without triggering a tool change. (both support_material_extruder and support_material_interface_extruder need to be set to 0)." @@ -9143,45 +9226,29 @@ msgid "There are unprintable objects. Try to adjust support settings to make the msgstr "Es gibt nicht druckbare Objekte. Versuchen Sie, die Stützeinstellungen anzupassen, um die Objekte druckbar zu machen." #: src/slic3r/GUI/DoubleSlider.cpp:1155 -msgid "" -"There is a color change for extruder that has not been used before.\n" -"Check your settings to avoid redundant color changes." -msgstr "" -"Es gibt einen Farbwechsel für den Extruder, der bisher noch nicht verwendet wurde.\n" -"Überprüfen Sie Ihre Einstellungen, um überflüssige Farbwechsel zu vermeiden." +msgid "There is a color change for extruder that has not been used before.\nCheck your settings to avoid redundant color changes." +msgstr "Es gibt einen Farbwechsel für den Extruder, der bisher noch nicht verwendet wurde.\nÜberprüfen Sie Ihre Einstellungen, um überflüssige Farbwechsel zu vermeiden." #: src/slic3r/GUI/DoubleSlider.cpp:1149 -msgid "" -"There is a color change for extruder that won't be used till the end of print job.\n" -"This code won't be processed during G-code generation." -msgstr "" -"Es gibt einen Farbwechsel für den Extruder, der nicht vor dem Ende des Druckauftrags verwendet wird.\n" -"Dieser Code wird bei der G-Code-Generierung nicht verarbeitet." +msgid "There is a color change for extruder that won't be used till the end of print job.\nThis code won't be processed during G-code generation." +msgstr "Es gibt einen Farbwechsel für den Extruder, der nicht vor dem Ende des Druckauftrags verwendet wird.\nDieser Code wird bei der G-Code-Generierung nicht verarbeitet." #: src/slic3r/GUI/DoubleSlider.cpp:1152 -msgid "" -"There is an extruder change set to the same extruder.\n" -"This code won't be processed during G-code generation." -msgstr "" -"Es gibt einen Extruderwechsel, der auf denselben Extruder eingestellt ist.\n" -"Dieser Code wird während der G-Code-Generierung nicht verarbeitet." +msgid "There is an extruder change set to the same extruder.\nThis code won't be processed during G-code generation." +msgstr "Es gibt einen Extruderwechsel, der auf denselben Extruder eingestellt ist.\nDieser Code wird während der G-Code-Generierung nicht verarbeitet." #: src/libslic3r/GCode.cpp:604 msgid "There is an object with no extrusions on the first layer." msgstr "Es gibt ein Objekt ohne Extrusionen in der ersten Schicht." #: src/slic3r/GUI/UpdateDialogs.cpp:225 -#, c-format +#, possible-c-format msgid "This %s version: %s" msgstr "Diese %s Version: %s" #: src/slic3r/GUI/Tab.cpp:1244 -msgid "" -"This action is not revertable.\n" -"Do you want to proceed?" -msgstr "" -"Diese Aktion ist nicht umkehrbar.\n" -"Wollen Sie fortfahren?" +msgid "This action is not revertable.\nDo you want to proceed?" +msgstr "Diese Aktion ist nicht umkehrbar.\nWollen Sie fortfahren?" #: src/libslic3r/PrintConfig.cpp:199 msgid "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want." @@ -9256,28 +9323,13 @@ msgid "This file cannot be loaded in a simple mode. Do you want to switch to an msgstr "Diese Datei kann nicht im einfachen Modus geladen werden. Möchten Sie in den fortgeschrittenen Modus wechseln?" #: src/slic3r/GUI/Plater.cpp:2319 -msgid "" -"This file contains several objects positioned at multiple heights.\n" -"Instead of considering them as multiple objects, should I consider\n" -"this file as a single object having multiple parts?" +msgid "This file contains several objects positioned at multiple heights.\nInstead of considering them as multiple objects, should I consider\nthis file as a single object having multiple parts?" msgstr "Diese Datei enthält mehrere Objekte, die in verschiedenen Höhen positioniert sind. Anstatt sie als mehrere Objekte zu betrachten, soll ich diese Datei als ein einzelnes Objekt mit mehreren Teilen betrachten?" #: src/slic3r/GUI/FirmwareDialog.cpp:332 -#, c-format -msgid "" -"This firmware hex file does not match the printer model.\n" -"The hex file is intended for: %s\n" -"Printer reported: %s\n" -"\n" -"Do you want to continue and flash this hex file anyway?\n" -"Please only continue if you are sure this is the right thing to do." -msgstr "" -"Diese Firmware-Hex-Datei stimmt nicht mit dem Druckermodell überein.\n" -"Die Hex-Datei ist für: %s\n" -"Drucker erkannt: %s\n" -"\n" -"Möchtest Sie fortfahren und diese Hex-Datei trotzdem flashen?\n" -"Bitte fahren Sie nur fort, wenn Sie der festen Überzeugung sind, dass dies das Richtige ist." +#, possible-c-format +msgid "This firmware hex file does not match the printer model.\nThe hex file is intended for: %s\nPrinter reported: %s\n\nDo you want to continue and flash this hex file anyway?\nPlease only continue if you are sure this is the right thing to do." +msgstr "Diese Firmware-Hex-Datei stimmt nicht mit dem Druckermodell überein.\nDie Hex-Datei ist für: %s\nDrucker erkannt: %s\n\nMöchtest Sie fortfahren und diese Hex-Datei trotzdem flashen?\nBitte fahren Sie nur fort, wenn Sie der festen Überzeugung sind, dass dies das Richtige ist." #: src/libslic3r/PrintConfig.cpp:348 msgid "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time." @@ -9347,12 +9399,16 @@ msgstr "Diese Stellung bestimmt die Beschleunigung des Druckers für Infill. Set msgid "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters." msgstr "Dies ist die Beschleunigung, die der Drucker für Außenkonturen benutzen wird. Ein hoher Wert wie 9000 ergibt üblicherweise gute Resultate falls Ihre Hardware mithalten kann. Setzen Sie dies auf null, um die Beschleunigungskontrolle bei Außenkonturen zu deaktivieren." +#: src/libslic3r/PrintConfig.cpp:1582 +msgid "This is the acceleration your printer will use for perimeters. Set zero to disable acceleration control for perimeters." +msgstr "Dies ist die Beschleunigung, die Ihr Drucker für Perimeter verwendet. Setzen Sie Null, um die Beschleunigungssteuerung für Perimeter zu deaktivieren." + #: src/libslic3r/PrintConfig.cpp:1435 msgid "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "Durchmesser der Extruderdüse (z.B.: 0.5, 0.35 usw.)" #: src/libslic3r/PrintConfig.cpp:1335 -#, c-format +#, possible-c-format msgid "This is the highest printable layer height for this extruder, used to cap the variable layer height and support layer height. Maximum recommended layer height is 75% of the extrusion width to achieve reasonable inter-layer adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." msgstr "Dies ist die höchste druckbare Schichthöhe für diesen Extruder, mit der die variable Schichthöhe und Stützschichthöhe abgedeckt wird. Die maximale empfohlene Schichthöhe beträgt 75% der Extrusionsbreite, um eine angemessene Zwischenlagenhaftung zu erreichen. Bei Einstellung auf 0 ist die Lagenhöhe auf 75% des Düsendurchmessers begrenzt." @@ -9369,12 +9425,8 @@ msgid "This matrix describes volumes (in cubic milimetres) required to purge the msgstr "Diese Matrix beschreibt die Volumina (in Kubikmillimetern), die benötigt werden, um das neue Filament auf dem Reinigungsturm für ein bestimmtes Werkzeugpaar zu reinigen." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:928 -msgid "" -"This operation is irreversible.\n" -"Do you want to proceed?" -msgstr "" -"Dieser Vorgang ist nicht mehr rückgängig zu machen.\n" -"Möchten Sie fortfahren?" +msgid "This operation is irreversible.\nDo you want to proceed?" +msgstr "Dieser Vorgang ist nicht mehr rückgängig zu machen.\nMöchten Sie fortfahren?" #: src/libslic3r/PrintConfig.cpp:1550 msgid "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled." @@ -9386,7 +9438,7 @@ msgstr "Mit dieser Option wird die Temperatur der inaktiven Extruder gesenkt, um #: src/libslic3r/PrintConfig.cpp:1073 msgid "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material). If enabled, slows down the G-code generation due to the multiple checks involved." -msgstr "Diese Einstellung beschränkt den Infill auf die Bereiche, die tatsächlich für das Stützen von Decken benötigt werden (der Infill dient hier als interne Stützstruktur). Falls aktiviert, kann dies die Erstellung des G-Codes wegen zusätzlichen Kontrollschritten verlangsamen." +msgstr "Diese Einstellung beschränkt den Infill auf die Bereiche, die tatsächlich für das Stützen von Decken benötigt werden (der Infill dient hier als internes Stützmaterial). Falls aktiviert, kann dies die Erstellung des G-Codes wegen zusätzlichen Kontrollschritten verlangsamen." #: src/libslic3r/PrintConfig.cpp:1066 msgid "This option will switch the print order of perimeters and infill, making the latter first." @@ -9441,17 +9493,9 @@ msgid "This vector saves required volumes to change from/to each tool used on th msgstr "Dieser Vektor speichert die erforderlichen Volumina für den Wechsel von/zu jedem am Reinigungsturm verwendeten Werkzeug. Diese Werte werden verwendet, um die Erstellung des vollen Reinigungsvolumens zu vereinfachen." #: src/slic3r/GUI/UpdateDialogs.cpp:216 -#, c-format -msgid "" -"This version of %s is not compatible with currently installed configuration bundles.\n" -"This probably happened as a result of running an older %s after using a newer one.\n" -"\n" -"You may either exit %s and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s." -msgstr "" -"Diese Version von %s ist nicht kompatibel zu den aktuell installierten Konfigurationssammlungen.\n" -"Dies wurde wahrscheinlich dadurch verursacht, dass Sie eine ältere %s Version benutzt haben, nachdem Sie eine neuere ausgeführt hatten.\n" -"\n" -"Sie können %s entweder beenden und es mit einer neueren Version nochmals versuchen, oder Sie können die erstmalige Startkonfiguration nochmals wiederholen. In diesem Fall wird eine Sicherungskopie der aktuellen Konfiguration erstellt, bevor die mit dieser %s-Version kompatiblen Dateien installiert werden." +#, possible-c-format +msgid "This version of %s is not compatible with currently installed configuration bundles.\nThis probably happened as a result of running an older %s after using a newer one.\n\nYou may either exit %s and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s." +msgstr "Diese Version von %s ist nicht kompatibel zu den aktuell installierten Konfigurationssammlungen.\nDies wurde wahrscheinlich dadurch verursacht, dass Sie eine ältere %s Version benutzt haben, nachdem Sie eine neuere ausgeführt hatten.\n\nSie können %s entweder beenden und es mit einer neueren Version nochmals versuchen, oder Sie können die erstmalige Startkonfiguration nochmals wiederholen. In diesem Fall wird eine Sicherungskopie der aktuellen Konfiguration erstellt, bevor die mit dieser %s-Version kompatiblen Dateien installiert werden." #: src/libslic3r/PrintConfig.cpp:2601 msgid "This will apply a gamma correction to the rasterized 2D polygons. A gamma value of zero means thresholding with the threshold in the middle. This behaviour eliminates antialiasing without losing holes in polygons." @@ -9527,10 +9571,14 @@ msgid "To use a custom CA file, please import your CA file into Certificate Stor msgstr "Um eine benutzerdefinierte CA-Datei zu verwenden, importieren Sie bitte Ihre CA-Datei in den Zertifikatsspeicher / Schlüsselbund." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:271 -#, c-format +#, possible-c-format msgid "Toggle %c axis mirroring" msgstr "Umschalten der Spiegelung der %c-Achse" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:215 +msgid "Toggle vertical slider one layer mode ON/OFF" +msgstr "Umschalten des vertikalen Schiebereglers für den Modus einer Schicht EIN/AUS" + #: src/libslic3r/miniz_extension.cpp:93 msgid "too many files" msgstr "zu viele Dateien" @@ -9611,6 +9659,10 @@ msgstr "Obere massive Schichten" msgid "Top View" msgstr "Ansicht von oben" +#: src/libslic3r/PrintConfig.cpp:1211 +msgid "Topmost surface only" +msgstr "Nur oberste Fläche" + #: src/slic3r/GUI/WipeTowerDialog.cpp:285 msgid "Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded." msgstr "Das gesamte Reinigungsvolumen wird durch die Addition folgender zwei Werte berechnet, je nachdem welche Werkzeuge geladen/entladen sind." @@ -9671,13 +9723,9 @@ msgid "Type:" msgstr "Typ:" #: src/slic3r/GUI/OpenGLManager.cpp:275 -#, c-format -msgid "" -"Unable to load the following shaders:\n" -"%s" -msgstr "" -"Die folgenden Shader konnten nicht geladen werden:\n" -"%s" +#, possible-c-format +msgid "Unable to load the following shaders:\n%s" +msgstr "Die folgenden Shader konnten nicht geladen werden:\n%s" #: src/slic3r/GUI/Plater.cpp:3233 msgid "Unable to reload:" @@ -9699,7 +9747,7 @@ msgid "Undo" msgstr "Undo" #: src/slic3r/GUI/GLCanvas3D.cpp:4382 -#, c-format +#, possible-c-format msgid "Undo %1$d Action" msgid_plural "Undo %1$d Actions" msgstr[0] "Undo %1$d Aktion" @@ -9745,20 +9793,12 @@ msgid "UNLOCKED LOCK" msgstr "OFFENES SCHLOSS" #: src/slic3r/GUI/Tab.cpp:3719 -msgid "" -"UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\n" -"Click to reset all settings for current option group to the system (or default) values." -msgstr "" -"Das Symbol GEÖFFNETES SCHLOSS zeigt an, dass einige Einstellungen geändert wurden und nicht mehr mit den System- (oder Standard-) Werte für die aktuelle Optionsgruppe identisch sind.\n" -"Klicken Sie, um alle Einstellungen für die aktuelle Optionsgruppe auf die System- (oder Standard-) Werte zurückzusetzen." +msgid "UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\nClick to reset all settings for current option group to the system (or default) values." +msgstr "Das Symbol GEÖFFNETES SCHLOSS zeigt an, dass einige Einstellungen geändert wurden und nicht mehr mit den System- (oder Standard-) Werte für die aktuelle Optionsgruppe identisch sind.\nKlicken Sie, um alle Einstellungen für die aktuelle Optionsgruppe auf die System- (oder Standard-) Werte zurückzusetzen." #: src/slic3r/GUI/Tab.cpp:3734 -msgid "" -"UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system (or default) value.\n" -"Click to reset current value to the system (or default) value." -msgstr "" -"Das Symbol GEÖFFNETES SCHLOSS zeigt an, dass der Wert geändert wurde und nicht mit der System- (oder Standard-) Einstellung identisch ist.\n" -"Klicken Sie, um den aktuellen Wert auf die System- (oder Standard-) Einstellung zurückzusetzen." +msgid "UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system (or default) value.\nClick to reset current value to the system (or default) value." +msgstr "Das Symbol GEÖFFNETES SCHLOSS zeigt an, dass der Wert geändert wurde und nicht mit der System- (oder Standard-) Einstellung identisch ist.\nKlicken Sie, um den aktuellen Wert auf die System- (oder Standard-) Einstellung zurückzusetzen." #: src/slic3r/GUI/KBShortcutsDialog.cpp:173 msgid "Unselect gizmo or clear selection" @@ -9797,7 +9837,7 @@ msgid "up to" msgstr "bis zu" #: src/slic3r/GUI/GLCanvas3D.cpp:961 -#, c-format +#, possible-c-format msgid "up to %.2f mm" msgstr "bis zu %.2f mm" @@ -9842,10 +9882,18 @@ msgstr "Lade hoch" msgid "Upper Layer" msgstr "Obere Schicht" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:218 +msgid "Upper layer" +msgstr "Obere Schicht" + #: src/slic3r/GUI/DoubleSlider.cpp:1276 msgid "Use another extruder" msgstr "Einen anderen Extruder verwenden" +#: src/slic3r/GUI/GLCanvas3D.cpp:3959 +msgid "Use CTRL+left mouse key to enter text edit mode:" +msgstr "STRG+linke Maustaste verwenden, um in den Textbearbeitungsmodus zu gelangen:" + #: src/slic3r/GUI/Preferences.cpp:220 msgid "Use custom size for toolbar icons" msgstr "Benutzerdefinierte Größe für Symbolleistensymbole verwenden" @@ -9900,7 +9948,7 @@ msgstr "Verwenden Sie diese Einstellung, um den Buchstaben der Achse anzugeben, #: src/libslic3r/PrintConfig.cpp:2035 msgid "Use this setting to rotate the support material pattern on the horizontal plane." -msgstr "Verwenden Sie diese Einstellung, um das Muster für die Stützstrukturen auf der horizontalen Ebene zu drehen." +msgstr "Verwenden Sie diese Einstellung, um das Muster des Stützmaterials auf der horizontalen Ebene zu drehen." #: src/libslic3r/PrintConfig.cpp:2318 msgid "Use volumetric E" @@ -10017,6 +10065,50 @@ msgstr "Version" msgid "Vertical shells" msgstr "Vertikale Konturhüllen" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:234 +msgid "Vertical slider - Add color change marker for current layer" +msgstr "Vertikaler Schieberegler - Fügt einen Farbwechselmarker der aktuellen Schicht hinzu" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:235 +msgid "Vertical slider - Delete color change marker for current layer" +msgstr "Vertikaler Schieberegler - Löscht den Farbwechselmarker der aktuellen Schicht" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:208 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:212 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:231 +msgid "Vertical slider - Move active thumb Down" +msgstr "Vertikaler Schieberegler - Aktiven Schieber nach unten bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:207 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:211 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 +msgid "Vertical slider - Move active thumb Up" +msgstr "Vertikaler Schieberegler - Aktiven Schieber nach oben bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:231 +msgid "Vertical slider - Move current thumb Down" +msgstr "Vertikaler Schieberegler - Aktuellen Schieber nach unten bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 +msgid "Vertical slider - Move current thumb Up" +msgstr "Vertikaler Schieberegler - Aktuellen Schieber nach oben bewegen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:233 +msgid "Vertical slider - Set lower thumb as active" +msgstr "Vertikaler Schieberegler - Unteren Schieber aktiv setzen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:233 +msgid "Vertical slider - Set lower thumb to current thumb" +msgstr "Vertikaler Schieberegler - Unteren Schieber auf aktuellen setzen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:232 +msgid "Vertical slider - Set upper thumb as active" +msgstr "Vertikaler Schieberegler - Oberen Schieber aktiv setzen" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:232 +msgid "Vertical slider - Set upper thumb to current thumb" +msgstr "Vertikaler Schieberegler - Oberen Schieber auf aktuellen setzen" + #: src/slic3r/GUI/GUI_Preview.cpp:265 src/slic3r/GUI/GUI_Preview.cpp:271 msgid "View" msgstr "Ansicht" @@ -10026,12 +10118,8 @@ msgid "View mode" msgstr "Anzeigemodus" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:666 -msgid "" -"Visit \"Preferences\" and check \"%1%\"\n" -"to be asked about unsaved changes again." -msgstr "" -"Besuchen Sie \"Präferenzen\" und überprüfen Sie \"%1%\",\n" -"um über nicht gespeicherte Änderungen wieder gefragt zu werden." +msgid "Visit \"Preferences\" and check \"%1%\"\nto be asked about unsaved changes again." +msgstr "Besuchen Sie \"Präferenzen\" und überprüfen Sie \"%1%\",\num über nicht gespeicherte Änderungen wieder gefragt zu werden." #: src/libslic3r/PrintConfig.cpp:3553 msgid "Visualize an already sliced and saved G-code" @@ -10040,7 +10128,7 @@ msgstr "Visualisierung eines bereits gesliceten und gespeicherten G-Codes" #: src/libslic3r/SLAPrintSteps.cpp:411 src/libslic3r/SLAPrintSteps.cpp:420 #: src/libslic3r/SLAPrintSteps.cpp:459 msgid "Visualizing supports" -msgstr "Anzeigen der Stützstrukturen" +msgstr "Anzeigen der Stützen" #: src/slic3r/GUI/Plater.cpp:167 msgid "Volume" @@ -10096,12 +10184,12 @@ msgid "Welcome" msgstr "Willkommen" #: src/slic3r/GUI/ConfigWizard.cpp:445 -#, c-format +#, possible-c-format msgid "Welcome to the %s Configuration Assistant" msgstr "Willkommen zum %s Konfigurations-Assistent" #: src/slic3r/GUI/ConfigWizard.cpp:447 -#, c-format +#, possible-c-format msgid "Welcome to the %s Configuration Wizard" msgstr "Willkommen zum %s Konfigurations-Assistent" @@ -10113,6 +10201,10 @@ msgstr "Was möchten Sie mit der Voreinstellung \"%1%\" nach dem Speichern mache msgid "When checked, the print and filament presets are shown in the preset editor even if they are marked as incompatible with the active printer" msgstr "Falls angekreuzt, werden Voreinstellungen für Druck und Filament im Voreinstellungseditor auch dann angezeigt, wenn sie als inkompatibel zum aktiven Drucker gekennzeichnet wurden" +#: src/slic3r/GUI/Preferences.cpp:122 +msgid "When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load." +msgstr "Wenn dieses Kontrollkästchen aktiviert ist, wird beim Ziehen und Ablegen einer Projektdatei auf die Anwendung ein Dialogfeld angezeigt, in dem Sie die Aktion auswählen können, die mit der zu ladenden Datei ausgeführt werden soll." + #: src/slic3r/GUI/Preferences.cpp:156 msgid "When closing the application, always ask for unsaved changes" msgstr "Wenn Sie die Anwendung schließen, immer nach nicht gespeicherten Änderungen fragen" @@ -10209,6 +10301,11 @@ msgstr "wird abgeschaltet." msgid "Will inflate or deflate the sliced 2D polygons according to the sign of the correction." msgstr "Vergrößert oder verringert die geslicten 2D-Polygone entsprechend dem Vorzeichen der Korrektur." +#: src/slic3r/GUI/GCodeViewer.cpp:2660 src/slic3r/GUI/GCodeViewer.cpp:2663 +#: src/slic3r/GUI/GUI_Preview.cpp:978 +msgid "Wipe" +msgstr "Reinigen" + #: src/libslic3r/PrintConfig.cpp:2404 msgid "Wipe into this object" msgstr "Dieses Objekt zum Reinigen verwenden" @@ -10275,18 +10372,8 @@ msgid "World coordinates" msgstr "Weltkoordinaten" #: src/slic3r/GUI/UpdateDialogs.cpp:92 -msgid "" -"Would you like to install it?\n" -"\n" -"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" -"\n" -"Updated configuration bundles:" -msgstr "" -"Möchten Sie dies installieren?\n" -"\n" -"Beachten Sie, dass zuerst eine Momentaufnahme der gesamten Konfiguration erstellt wird. Diese kann dann jederzeit wiederhergestellt werden, falls es ein Problem mit der neuen Version gibt.\n" -"\n" -"Aktualisierte Konfigurationssammlungen:" +msgid "Would you like to install it?\n\nNote that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n\nUpdated configuration bundles:" +msgstr "Möchten Sie dies installieren?\n\nBeachten Sie, dass zuerst eine Momentaufnahme der gesamten Konfiguration erstellt wird. Diese kann dann jederzeit wiederhergestellt werden, falls es ein Problem mit der neuen Version gibt.\n\nAktualisierte Konfigurationssammlungen:" #: src/libslic3r/miniz_extension.cpp:151 msgid "write calledback failed" @@ -10357,7 +10444,7 @@ msgid "You can't change a type of the last solid part of the object." msgstr "Sie können nicht die Art des letzten soliden Teils des Objektes ändern." #: src/slic3r/GUI/Plater.cpp:2352 -#, c-format +#, possible-c-format msgid "You can't to add the object(s) from %s because of one or some of them is(are) multi-part" msgstr "Sie können die Objekte aus %s nicht hinzufügen, weil eines oder einige von ihnen mehrteilig ist (sind)" @@ -10370,12 +10457,8 @@ msgid "You cannot use non-uniform scaling mode for multiple objects/parts select msgstr "Sie können den nichtgleichmäßigen Skalierungsmodus nicht für mehrere Objekte/Teileauswahlen verwenden" #: src/slic3r/GUI/SavePresetDialog.cpp:277 -msgid "" -"You have selected physical printer \"%1%\" \n" -"with related printer preset \"%2%\"" -msgstr "" -"Sie haben den physischen Drucker \"%1%\" ausgewählt \n" -"mit der zugehörigen Druckervoreinstellung \"%2%\"." +msgid "You have selected physical printer \"%1%\" \nwith related printer preset \"%2%\"" +msgstr "Sie haben den physischen Drucker \"%1%\" ausgewählt \nmit der zugehörigen Druckervoreinstellung \"%2%\"." #: src/slic3r/GUI/GUI_App.cpp:1078 msgid "You have the following presets with saved options for \"Print Host upload\"" @@ -10390,7 +10473,7 @@ msgid "You must install a configuration update." msgstr "Ein Konfigurations-Update muss installiert werden." #: src/slic3r/GUI/Preferences.cpp:299 -#, c-format +#, possible-c-format msgid "You need to restart %s to make the changes effective." msgstr "Sie müssen %s neu starten, damit die Änderungen wirksam werden." @@ -10399,7 +10482,7 @@ msgid "You should to change a name of your printer device. It can't be saved." msgstr "Sie sollten den Namen Ihres Druckers ändern. Er kann nicht gespeichert werden." #: src/slic3r/GUI/GUI_ObjectList.cpp:3884 -#, c-format +#, possible-c-format msgid "You started your selection with %s Item." msgstr "Sie haben Ihre Auswahl mit %s Elementen begonnen." @@ -10432,24 +10515,12 @@ msgid "Z offset" msgstr "Z-Abstand" #: src/slic3r/GUI/ConfigManipulation.cpp:59 -msgid "" -"Zero first layer height is not valid.\n" -"\n" -"The first layer height will be reset to 0.01." -msgstr "" -"Null Höhe der ersten Schicht ist nicht gültig.\n" -"\n" -"Die erste Schichthöhe wird auf 0,01 zurückgesetzt." +msgid "Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01." +msgstr "Null Höhe der ersten Schicht ist nicht gültig.\n\nDie erste Schichthöhe wird auf 0,01 zurückgesetzt." #: src/slic3r/GUI/ConfigManipulation.cpp:47 -msgid "" -"Zero layer height is not valid.\n" -"\n" -"The layer height will be reset to 0.01." -msgstr "" -"Eine Nullschichthöhe ist nicht gültig.\n" -"\n" -"Die Schichthöhe wird auf 0,01 zurückgesetzt." +msgid "Zero layer height is not valid.\n\nThe layer height will be reset to 0.01." +msgstr "Eine Nullschichthöhe ist nicht gültig.\n\nDie Schichthöhe wird auf 0,01 zurückgesetzt." #: src/libslic3r/PrintConfig.cpp:2831 msgid "Zig-Zag" @@ -10473,12 +10544,8 @@ msgid "Zoom to Bed" msgstr "Zoom aufs Druckbett" #: src/slic3r/GUI/KBShortcutsDialog.cpp:176 -msgid "" -"Zoom to selected object\n" -"or all objects in scene, if none selected" -msgstr "" -"Auf ausgewähltes Objekt zoomen\n" -"oder alle Objekte in der Szene, wenn keines ausgewählt ist" +msgid "Zoom to selected object\nor all objects in scene, if none selected" +msgstr "Auf ausgewähltes Objekt zoomen\noder alle Objekte in der Szene, wenn keines ausgewählt ist" #: src/libslic3r/PrintConfig.cpp:241 src/libslic3r/PrintConfig.cpp:816 #: src/libslic3r/PrintConfig.cpp:1748 src/libslic3r/PrintConfig.cpp:1758 diff --git a/resources/localization/es/PrusaSlicer.mo b/resources/localization/es/PrusaSlicer.mo index 43aaac113a429eb2d67497fd0d1bf2fb4b40488d..c6e94f134d999a85823182c8838962a8bf7fecf4 100644 GIT binary patch delta 64369 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 delta 53249 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 F{XbBimNin~jTL$M+)E=5|jxNFf1-~ZV& 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 delta 52927 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%0AkKF_XFsMyJ@_)}!Ov~|Z`4j2f0l_v3e;3&!-H59 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 1);\n" -"X-Generator: Poedit 2.4.2\n" -"Project-Id-Version: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language-Team: \n" +"X-Generator: PhraseApp (phraseapp.com)\n" #: src/slic3r/GUI/Tab.cpp:4124 -msgid "" -"\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" -"To enable \"%1%\", please switch off \"%2%\"" -msgstr "" -"\"%1%\" est désactivé car \"%2%\" est activé dans la catégorie \"%3%\".\n" -"Pour activer \"%1%\", veuillez désactiver \"%2%\"" +msgid "\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\nTo enable \"%1%\", please switch off \"%2%\"" +msgstr "\"%1%\" est désactivé car \"%2%\" est activé dans la catégorie \"%3%\".\nPour activer \"%1%\", veuillez désactiver \"%2%\"" #: src/libslic3r/PrintConfig.cpp:249 src/libslic3r/PrintConfig.cpp:828 #: src/libslic3r/PrintConfig.cpp:1148 src/libslic3r/PrintConfig.cpp:1327 @@ -29,7 +20,7 @@ msgid "%" msgstr "%" #: src/slic3r/GUI/GLCanvas3D.cpp:969 -#, c-format +#, possible-c-format msgid "%.2f - %.2f mm" msgstr "%.2f - %.2f mm" @@ -51,140 +42,113 @@ msgid "%1%=%2% mm is too low to be printable at a layer height %3% mm" msgstr "%1%=%2% mm est trop bas pour être imprimable avec une hauteur de couche de %3% mm" #: src/slic3r/GUI/PresetHints.cpp:228 -#, c-format +#, possible-c-format msgid "%3.2f mm³/s at filament speed %3.2f mm/s." msgstr "%3.2f mm³/s à une vitesse de filament de %3.2f mm/s." #: src/slic3r/GUI/Plater.cpp:1061 -#, c-format +#, possible-c-format msgid "%d (%d shells)" msgstr "%d (%d coques)" #: src/slic3r/GUI/Plater.cpp:1069 -#, c-format +#, possible-c-format msgid "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges" msgstr "%d faces invalides, %d arrêtes corrigées, %d faces retirées, %d faces ajoutées, %d faces inversées, %d arrêtes à l'envers" #: src/slic3r/GUI/PresetHints.cpp:269 -#, c-format +#, possible-c-format msgid "%d lines: %.2f mm" msgstr "%d lignes : %.2f mm" #: src/slic3r/GUI/MainFrame.cpp:1728 -#, c-format +#, possible-c-format msgid "%d presets successfully imported." msgstr "%d préréglages importés avec succès." #: src/slic3r/GUI/GUI_App.cpp:718 -#, c-format -msgid "" -"%s\n" -"Do you want to continue?" -msgstr "" -"%s\n" -"Voulez-vous continuer ?" +#, possible-c-format +msgid "%s\nDo you want to continue?" +msgstr "%s\nVoulez-vous continuer ?" #: src/slic3r/GUI/MainFrame.cpp:917 src/slic3r/GUI/MainFrame.cpp:1316 -#, c-format +#, possible-c-format msgid "%s &Website" msgstr "Site &Web de %s" #: src/slic3r/GUI/GUI_App.cpp:394 -#, c-format +#, possible-c-format msgid "%s - BREAKING CHANGE" msgstr "%s - MODIFICATION IMPACTANTE" +#: src/slic3r/GUI/Plater.cpp:1410 +#, possible-c-format +msgid "%s - Drop project file" +msgstr "%s - Déposer le fichier de projet" + #: src/slic3r/GUI/UpdateDialogs.cpp:211 -#, c-format +#, possible-c-format msgid "%s configuration is incompatible" msgstr "La configuration de %s n'est pas compatible" #: src/slic3r/GUI/Field.cpp:223 -#, c-format +#, possible-c-format msgid "%s doesn't support percentage" msgstr "%s ne supporte pas un pourcentage" #: src/slic3r/GUI/MsgDialog.cpp:73 -#, c-format +#, possible-c-format msgid "%s error" msgstr "Erreur %s" #: src/slic3r/GUI/ConfigWizard.cpp:499 -#, c-format +#, possible-c-format msgid "%s Family" -msgstr "%s Famille" +msgstr "Famille %s" #: src/slic3r/GUI/MsgDialog.cpp:74 -#, c-format +#, possible-c-format msgid "%s has encountered an error" msgstr "%s a rencontré une erreur" #: src/slic3r/GUI/GUI_App.cpp:528 -#, c-format -msgid "" -"%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it.\n" -"\n" -"The application will now terminate." -msgstr "" -"%s a rencontré une erreur. Elle a apparemment été provoquée par un manque de mémoire. Si vous êtes certain d'avoir assez de RAM sur votre système, cela peut également être un bug et nous aimerions que vous le signaliez.\n" -"\n" -"L'application va maintenant fermer." +#, possible-c-format +msgid "%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it.\n\nThe application will now terminate." +msgstr "%s a rencontré une erreur. Elle a apparemment été provoquée par un manque de mémoire. Si vous êtes certain d'avoir assez de RAM sur votre système, cela peut également être un bug et nous aimerions que vous le signaliez.\n\nL'application va maintenant fermer." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:62 -#, c-format +#, possible-c-format msgid "%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it." msgstr "%s a rencontré une erreur. Elle a apparemment été provoquée par un manque de mémoire. Si vous êtes certain d'avoir assez de RAM sur votre système, cela peut également être un bug et nous aimerions que vous le signaliez." #: src/slic3r/GUI/UpdateDialogs.cpp:309 -#, c-format +#, possible-c-format msgid "%s has no configuration updates available." msgstr "%s n'a aucunes mises à jour de configuration disponibles." #: src/slic3r/GUI/UpdateDialogs.cpp:148 src/slic3r/GUI/UpdateDialogs.cpp:210 -#, c-format +#, possible-c-format msgid "%s incompatibility" msgstr "Incompatibilité de %s" #: src/slic3r/GUI/UpdateDialogs.cpp:270 -#, c-format -msgid "" -"%s now uses an updated configuration structure.\n" -"\n" -"So called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\n" -"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n" -"\n" -"Please proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." -msgstr "" -"%s utilise à présent une structure de configuration mise à jour.\n" -"\n" -"Il existe à présent des \"préréglages Système\", qui intègrent les réglages par défaut pour les différentes imprimantes. Ces préréglages Système ne peuvent pas être modifiés, mais les utilisateurs peuvent désormais créer leurs propres préréglages héritant des paramètres de l'un des préréglages Système.\n" -"Un tel préréglage peut ainsi hériter d'une valeur particulière de son parent ou la remplacer par une valeur personnalisée.\n" -"\n" -"Veuillez utiliser les %s qui suivent pour paramétrer les nouveaux réglages et éventuellement accepter les mises à jour de réglage automatiques." +#, possible-c-format +msgid "%s now uses an updated configuration structure.\n\nSo called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\nAn inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\nPlease proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." +msgstr "%s utilise à présent une structure de configuration mise à jour.\n\nIl existe à présent des \"préréglages Système\", qui intègrent les réglages par défaut pour les différentes imprimantes. Ces préréglages Système ne peuvent pas être modifiés, mais les utilisateurs peuvent désormais créer leurs propres préréglages héritant des paramètres de l'un des préréglages Système.\nUn tel préréglage peut ainsi hériter d'une valeur particulière de son parent ou la remplacer par une valeur personnalisée.\n\nVeuillez utiliser les %s qui suivent pour paramétrer les nouveaux réglages et éventuellement accepter les mises à jour de réglage automatiques." #: src/slic3r/GUI/GUI_App.cpp:1512 -#, c-format +#, possible-c-format msgid "%s View Mode" msgstr "Mode de Vue de %s" #: src/slic3r/GUI/UpdateDialogs.cpp:151 -#, c-format -msgid "" -"%s will now start updates. Otherwise it won't be able to start.\n" -"\n" -"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" -"\n" -"Updated configuration bundles:" -msgstr "" -"%s va maintenant démarrer les mises à jour. Sinon, il ne pourra pas démarrer.\n" -"\n" -"Notez qu'un instantané complet de la configuration sera créé en premier. Il peut ensuite être restauré à tout moment en cas de problème avec la nouvelle version.\n" -"\n" -"Lots de configuration mis à jour :" +#, possible-c-format +msgid "%s will now start updates. Otherwise it won't be able to start.\n\nNote that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n\nUpdated configuration bundles:" +msgstr "%s va maintenant démarrer les mises à jour. Sinon, il ne pourra pas démarrer.\n\nNotez qu'un instantané complet de la configuration sera créé en premier. Il peut ensuite être restauré à tout moment en cas de problème avec la nouvelle version.\n\nLots de configuration mis à jour :" #: src/slic3r/GUI/MainFrame.cpp:933 src/slic3r/GUI/MainFrame.cpp:937 #: src/slic3r/GUI/MainFrame.cpp:1329 -#, c-format +#, possible-c-format msgid "&About %s" msgstr "&Au sujet de %s" @@ -312,6 +276,10 @@ msgstr "Fenê&tre" msgid "(All)" msgstr "(Tout)" +#: src/slic3r/GUI/Plater.cpp:1195 +msgid "(including spool)" +msgstr "(bobine incluse)" + #: src/libslic3r/PrintConfig.cpp:1554 msgid "(minimum)" msgstr "(minimum)" @@ -328,10 +296,22 @@ msgstr "(Re)Découper Main&tenant" msgid "(Unknown)" msgstr "(Inconnu)" +#: src/slic3r/GUI/Plater.cpp:1195 +msgid "(weight with spool)" +msgstr "(poids avec la bobine)" + #: src/slic3r/GUI/MainFrame.cpp:1491 msgid ") not found." msgstr ") non trouvé." +#: src/libslic3r/PrintConfig.cpp:1085 +msgid "0 (no open anchors)" +msgstr "0 (aucune ancre ouverte)" + +#: src/libslic3r/PrintConfig.cpp:1107 +msgid "0 (not anchored)" +msgstr "0 (non ancré)" + #: src/libslic3r/PrintConfig.cpp:2060 msgid "0 (soluble)" msgstr "0 (soluble)" @@ -340,6 +320,10 @@ msgstr "0 (soluble)" msgid "0.2 (detachable)" msgstr "0.2 (détachable)" +#: src/libslic3r/PrintConfig.cpp:1090 src/libslic3r/PrintConfig.cpp:1112 +msgid "1000 (unlimited)" +msgstr "1000 (illimité)" + #: src/slic3r/GUI/MainFrame.cpp:1234 msgid "3&D" msgstr "3&D" @@ -361,7 +345,7 @@ msgid "3Dconnexion settings" msgstr "Paramètres 3Dconnexion" #: src/slic3r/GUI/Plater.cpp:5167 -#, c-format +#, possible-c-format msgid "3MF file exported to %s" msgstr "Fichier 3MF exporté vers %s" @@ -394,7 +378,7 @@ msgid "A toolpath outside the print area was detected." msgstr "Un parcours d'outil en dehors de la zone d'impression a été détecté." #: src/slic3r/GUI/AboutDialog.cpp:212 src/slic3r/GUI/AboutDialog.cpp:215 -#, c-format +#, possible-c-format msgid "About %s" msgstr "Au sujet de %s" @@ -403,7 +387,7 @@ msgid "above" msgstr "au-dessus" #: src/slic3r/GUI/GLCanvas3D.cpp:965 -#, c-format +#, possible-c-format msgid "above %.2f mm" msgstr "au dessus de %.2f mm" @@ -424,6 +408,10 @@ msgstr "Précision" msgid "Accurate" msgstr "Précis" +#: src/slic3r/GUI/Plater.cpp:1423 +msgid "Action" +msgstr "Action" + #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:78 msgid "Activate" msgstr "Activer" @@ -540,7 +528,7 @@ msgid "Add modifier" msgstr "Ajouter un modificateur" #: src/libslic3r/PrintConfig.cpp:515 -#, c-format +#, possible-c-format msgid "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r keeps adding perimeters, until more than 70% of the loop immediately above is supported." msgstr "Ajouter plus de périmètres si nécessaire pour éviter des trous dans les parois inclinées. Slic3r ajoute des périmètres, jusqu'à ce que plus de 70% de la boucle immédiatement au-dessus soit supportée." @@ -711,6 +699,10 @@ msgstr "Aligner XY" msgid "Aligned" msgstr "Aligné" +#: src/libslic3r/PrintConfig.cpp:470 src/libslic3r/PrintConfig.cpp:902 +msgid "Aligned Rectilinear" +msgstr "Rectiligne Aligné" + #: src/slic3r/GUI/ConfigWizard.cpp:308 src/slic3r/GUI/ConfigWizard.cpp:598 #: src/slic3r/GUI/Tab.cpp:3507 src/slic3r/GUI/UnsavedChangesDialog.cpp:921 msgid "All" @@ -736,10 +728,18 @@ msgstr "Tous les objets seront supprimés, continuer ?" msgid "All settings changes will be discarded." msgstr "Tous les changements de réglages seront perdus." +#: src/libslic3r/PrintConfig.cpp:1212 +msgid "All solid surfaces" +msgstr "Toutes les surfaces solides" + #: src/slic3r/GUI/ConfigWizard.cpp:307 msgid "All standard" msgstr "Tout en standard" +#: src/libslic3r/PrintConfig.cpp:1210 +msgid "All top surfaces" +msgstr "Toutes les surfaces supérieures" + #: src/libslic3r/miniz_extension.cpp:121 msgid "allocation failed" msgstr "échec de l'allocation" @@ -770,17 +770,13 @@ msgid "Always ask for unsaved changes when selecting new preset" msgstr "Toujours demander pour les modifications non enregistrées lors de la sélection d'un nouveau préréglage" #: src/slic3r/GUI/Plater.cpp:5135 -#, c-format +#, possible-c-format msgid "AMF file exported to %s" msgstr "Fichier AMF exporté vers %s" #: src/slic3r/GUI/GLCanvas3D.cpp:638 -msgid "" -"An object outside the print area was detected.\n" -"Resolve the current problem to continue slicing." -msgstr "" -"Un objet en dehors de la zone d'impression a été détecté.\n" -"Résolvez le problème actuel pour continuer le découpage." +msgid "An object outside the print area was detected.\nResolve the current problem to continue slicing." +msgstr "Un objet en dehors de la zone d'impression a été détecté.\nRésolvez le problème actuel pour continuer le découpage." #: src/slic3r/GUI/GLCanvas3D.cpp:633 msgid "An object outside the print area was detected." @@ -799,6 +795,10 @@ msgstr "N'importe quelle flèche" msgid "Any modifications should be saved as a new preset inherited from this one." msgstr "Toute modification doit être enregistrée comme un nouveau préréglage hérité de celui-ci." +#: src/libslic3r/PrintConfig.cpp:162 +msgid "API key" +msgstr "Clé API" + #: src/libslic3r/PrintConfig.cpp:106 msgid "API Key / Password" msgstr "Clé API / Mot de Passe" @@ -829,12 +829,8 @@ msgid "Are you sure you want to %1% the selected preset?" msgstr "Êtes-vous sûr de vouloir %1% le préréglage sélectionné ?" #: src/slic3r/GUI/FirmwareDialog.cpp:902 -msgid "" -"Are you sure you want to cancel firmware flashing?\n" -"This could leave your printer in an unusable state!" -msgstr "" -"Êtes-vous certain de vouloir annuler le processus de flash du firmware ?\n" -"Cela pourrait rendre votre imprimante inutilisable !" +msgid "Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!" +msgstr "Êtes-vous certain de vouloir annuler le processus de flash du firmware ?\nCela pourrait rendre votre imprimante inutilisable !" #: src/slic3r/GUI/DoubleSlider.cpp:2122 src/slic3r/GUI/DoubleSlider.cpp:2142 msgid "Are you sure you want to continue?" @@ -865,6 +861,10 @@ msgstr "Autour de l'objet" msgid "Arrange" msgstr "Agencer" +#: src/slic3r/GUI/GLCanvas3D.cpp:3889 +msgid "Arrange options" +msgstr "Options d'agencement" + #: src/slic3r/GUI/GLCanvas3D.cpp:4859 src/slic3r/GUI/KBShortcutsDialog.cpp:152 msgid "Arrange selection" msgstr "Agencer la sélection" @@ -927,6 +927,18 @@ msgstr "Demander pour les modifications non enregistrées lors de la fermeture d msgid "Ask for unsaved changes when selecting new preset" msgstr "Demander pour les modifications non enregistrées lors de la sélection d'un nouveau préréglage" +#: src/slic3r/GUI/ConfigWizard.cpp:1183 src/slic3r/GUI/Preferences.cpp:91 +msgid "Associate .3mf files to PrusaSlicer" +msgstr "Associer les fichiers .3mf à PrusaSlicer" + +#: src/slic3r/GUI/Preferences.cpp:177 +msgid "Associate .gcode files to PrusaSlicer G-code Viewer" +msgstr "Associer les fichiers .gcode à à la prévisualisation de G-code de PrusaSlicer" + +#: src/slic3r/GUI/ConfigWizard.cpp:1184 src/slic3r/GUI/Preferences.cpp:98 +msgid "Associate .stl files to PrusaSlicer" +msgstr "Associer les fichiers .stl à PrusaSlicer" + #: src/slic3r/GUI/GUI_App.cpp:1878 src/slic3r/GUI/Jobs/SLAImportJob.cpp:210 #: src/slic3r/GUI/Plater.cpp:2256 src/slic3r/GUI/Tab.cpp:3189 msgid "Attention!" @@ -950,12 +962,12 @@ msgid "Auto-generate points" msgstr "Générer automatiquement les points" #: src/slic3r/GUI/Plater.cpp:1066 -#, c-format +#, possible-c-format msgid "Auto-repaired (%d errors)" msgstr "Réparé automatiquement (%d erreurs)" #: src/slic3r/GUI/GUI_ObjectList.cpp:386 -#, c-format +#, possible-c-format msgid "Auto-repaired (%d errors):" msgstr "Réparé automatiquement (%d erreurs):" @@ -1004,20 +1016,12 @@ msgid "BACK ARROW" msgstr "FLÈCHE ARRIÈRE" #: src/slic3r/GUI/Tab.cpp:3727 -msgid "" -"BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" -"Click to reset all settings for the current option group to the last saved preset." -msgstr "" -"L'icône FLÈCHE ARRIÈRE indique que les paramètres ont été changés et qu'ils ne sont pas identiques au dernier préréglage enregistré du groupe d'options en cours.\n" -"Cliquez pour restaurer tous les paramètres du groupe d'options en cours avec les valeurs du dernier préréglage enregistré." +msgid "BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\nClick to reset all settings for the current option group to the last saved preset." +msgstr "L'icône FLÈCHE ARRIÈRE indique que les paramètres ont été changés et qu'ils ne sont pas identiques au dernier préréglage enregistré du groupe d'options en cours.\nCliquez pour restaurer tous les paramètres du groupe d'options en cours avec les valeurs du dernier préréglage enregistré." #: src/slic3r/GUI/Tab.cpp:3741 -msgid "" -"BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" -"Click to reset current value to the last saved preset." -msgstr "" -"L'icône FLÈCHE ARRIÈRE indique que la valeur a été changée et qu'elle n'est pas identique au dernier préréglage enregistré.\n" -"Cliquez pour restaurer la valeur à celle du dernier préréglage enregistré." +msgid "BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\nClick to reset current value to the last saved preset." +msgstr "L'icône FLÈCHE ARRIÈRE indique que la valeur a été changée et qu'elle n'est pas identique au dernier préréglage enregistré.\nCliquez pour restaurer la valeur à celle du dernier préréglage enregistré." #: src/slic3r/GUI/Preferences.cpp:72 msgid "Background processing" @@ -1217,24 +1221,16 @@ msgid "buffer too small" msgstr "buffer trop petit" #: src/slic3r/GUI/GUI_App.cpp:1152 -msgid "" -"But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" -"Settings will be available in physical printers settings." -msgstr "" -"Mais depuis cette version de PrusaSlicer, nous ne montrons plus ces informations dans les Réglages de l'imprimante.\n" -"Les réglages seront disponibles dans les réglages des imprimantes physiques." +msgid "But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\nSettings will be available in physical printers settings." +msgstr "Mais depuis cette version de PrusaSlicer, nous ne montrons plus ces informations dans les Réglages de l'imprimante.\nLes réglages seront disponibles dans les réglages des imprimantes physiques." #: src/slic3r/GUI/ButtonsDescription.cpp:16 msgid "Buttons And Text Colors Description" msgstr "Description des Boutons et des Couleurs de Texte" #: src/slic3r/GUI/GUI_App.cpp:1084 -msgid "" -"By default new Printer devices will be named as \"Printer N\" during its creation.\n" -"Note: This name can be changed later from the physical printers settings" -msgstr "" -"Par défaut, les nouvelles imprimantes seront nommées \"Imprimante N\" lors de leur création.\n" -"Remarque : ce nom peut être modifié ultérieurement dans les réglages des imprimantes physiques" +msgid "By default new Printer devices will be named as \"Printer N\" during its creation.\nNote: This name can be changed later from the physical printers settings" +msgstr "Par défaut, les nouvelles imprimantes seront nommées \"Imprimante N\" lors de leur création.\nRemarque : ce nom peut être modifié ultérieurement dans les réglages des imprimantes physiques" #: src/slic3r/GUI/PresetHints.cpp:222 msgid "by the print profile maximum" @@ -1276,30 +1272,16 @@ msgid "Cannot calculate extrusion width for %1%: Variable \"%2%\" not accessible msgstr "Impossible de calculer la largeur d'extrusion pour %1% : la variable \"%2%\" n'est pas accessible." #: src/slic3r/GUI/GUI_ObjectList.cpp:3400 -msgid "" -"Cannot insert a new layer range after the current layer range.\n" -"Current layer range overlaps with the next layer range." -msgstr "" -"Impossible d'insérer une nouvelle zone de couche après la zone de couche actuelle.\n" -"La zone de couche actuelle chevauche la prochaine zone de couche." +msgid "Cannot insert a new layer range after the current layer range.\nCurrent layer range overlaps with the next layer range." +msgstr "Impossible d'insérer une nouvelle zone de couche après la zone de couche actuelle.\nLa zone de couche actuelle chevauche la prochaine zone de couche." #: src/slic3r/GUI/GUI_ObjectList.cpp:3391 -msgid "" -"Cannot insert a new layer range after the current layer range.\n" -"The next layer range is too thin to be split to two\n" -"without violating the minimum layer height." -msgstr "" -"Impossible d'insérer une nouvelle zone de couche après la zone de couche actuelle.\n" -"La zone de couche suivante est trop fine pour être séparée en deux sans enfreindre la hauteur de couche minimum." +msgid "Cannot insert a new layer range after the current layer range.\nThe next layer range is too thin to be split to two\nwithout violating the minimum layer height." +msgstr "Impossible d'insérer une nouvelle zone de couche après la zone de couche actuelle.\nLa zone de couche suivante est trop fine pour être séparée en deux sans enfreindre la hauteur de couche minimum." #: src/slic3r/GUI/GUI_ObjectList.cpp:3395 -msgid "" -"Cannot insert a new layer range between the current and the next layer range.\n" -"The gap between the current layer range and the next layer range\n" -"is thinner than the minimum layer height allowed." -msgstr "" -"Impossible d'insérer une nouvelle zone de couche entre l'actuelle et la prochaine.\n" -"L'espace entre la zone de couche actuelle et la prochaine est inférieur à la hauteur de couche minimum autorisée." +msgid "Cannot insert a new layer range between the current and the next layer range.\nThe gap between the current layer range and the next layer range\nis thinner than the minimum layer height allowed." +msgstr "Impossible d'insérer une nouvelle zone de couche entre l'actuelle et la prochaine.\nL'espace entre la zone de couche actuelle et la prochaine est inférieur à la hauteur de couche minimum autorisée." #: src/slic3r/GUI/SavePresetDialog.cpp:137 msgid "Cannot overwrite a system profile." @@ -1366,7 +1348,7 @@ msgid "Change Extruders" msgstr "Changer les Extrudeurs" #: src/slic3r/GUI/GUI_ObjectSettings.cpp:157 -#, c-format +#, possible-c-format msgid "Change Option %s" msgstr "Modifier l'Option %s" @@ -1451,10 +1433,18 @@ msgstr "Cercle" msgid "Circular" msgstr "Circulaire" +#: src/slic3r/GUI/GLCanvas3D.cpp:3961 +msgid "Clearance size" +msgstr "Espacement" + #: src/slic3r/GUI/GLCanvas3D.cpp:5028 src/slic3r/GUI/GLCanvas3D.cpp:5067 msgid "Click right mouse button to open/close History" msgstr "Cliquez avec le bouton droit de la souris pour ouvrir/fermer l'historique" +#: src/slic3r/GUI/GLCanvas3D.cpp:4341 +msgid "Click right mouse button to show arrangement options" +msgstr "Faites un clic droit pour afficher les options d'agencement" + #: src/slic3r/GUI/GUI_ObjectList.cpp:451 msgid "Click the icon to change the object printable property" msgstr "Cliquez sur l'icône pour changer les propriétés imprimables de l'objet" @@ -1522,7 +1512,7 @@ msgid "Color change (\"%1%\") for Extruder %2%" msgstr "Changement de couleur (\"%1%\") pour l'extrudeur %2%" #: src/slic3r/GUI/GLCanvas3D.cpp:1001 -#, c-format +#, possible-c-format msgid "Color change for Extruder %d at %.2f mm" msgstr "Changement de couleur pour l'Extrudeur %d à %.2f mm" @@ -1639,6 +1629,14 @@ msgstr "Assistant de Configuration" msgid "Confirmation" msgstr "Confirmation" +#: src/libslic3r/PrintConfig.cpp:1070 +msgid "Connect an infill line to an internal perimeter with a short segment of an additional perimeter. If expressed as percentage (example: 15%) it is calculated over infill extrusion width. PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment shorter than infill_anchor_max is found, the infill line is connected to a perimeter segment at just one side and the length of the perimeter segment taken is limited to this parameter, but no longer than anchor_length_max. Set this parameter to zero to disable anchoring perimeters connected to a single infill line." +msgstr "Connecte une ligne de remplissage à un périmètre interne avec un segment court de périmètre additionnel. Si exprimé en pourcentage (exemple : 15%), est calculé sur la largeur d'extrusion de remplissage. PrusaSlicer essaie de connecter deux lignes de remplissage proches à un court segment de périmètre. Si aucun segment de périmètre plus court que infill_anchor_max ne peut être trouvé, la ligne de remplissage est connectée à un segment de périmètre seulement d'un côté et la longueur du segment de périmètre considéré est limitée à ce paramètre, mais pas plus longue que anchor_length_max. Réglez ce paramètre sur zéro pour désactiver l'ancrage de périmètres connectés à une seule ligne de remplissage." + +#: src/libslic3r/PrintConfig.cpp:1097 +msgid "Connect an infill line to an internal perimeter with a short segment of an additional perimeter. If expressed as percentage (example: 15%) it is calculated over infill extrusion width. PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment shorter than this parameter is found, the infill line is connected to a perimeter segment at just one side and the length of the perimeter segment taken is limited to infill_anchor, but no longer than this parameter. Set this parameter to zero to disable anchoring." +msgstr "Connecte une ligne de remplissage à un périmètre interne avec un segment court de périmètre additionnel. Si exprimé en pourcentage (exemple : 15%), est calculé sur la largeur d'extrusion de remplissage. PrusaSlicer essaie de connecter deux lignes de remplissage proches à un court segment de périmètre. Si aucun segment de périmètre plus court que ce paramètre ne peut être trouvé, la ligne de remplissage est connectée à un segment de périmètre seulement d'un côté et la longueur du segment de périmètre considéré est limitée à infill_anchor, mais pas plus longue que ce paramètre. Réglez ce paramètre sur zéro pour désactiver l'ancrage." + #: src/slic3r/GUI/Tab.cpp:4046 msgid "Connection of the support sticks and junctions" msgstr "Connexion des tiges de support et jonctions" @@ -1744,12 +1742,8 @@ msgid "Copying of the temporary G-code to the output G-code failed" msgstr "La copie du G-code provisoire dans le G-code final a échoué" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:163 -msgid "" -"Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\n" -"Error message: %1%" -msgstr "" -"La copie du G-code temporaire vers le G-code de sortie a échoué. Peut-être que la carte SD est verrouillée en écriture ?\n" -"Message d'erreur : %1%" +msgid "Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%" +msgstr "La copie du G-code temporaire vers le G-code de sortie a échoué. Peut-être que la carte SD est verrouillée en écriture ?\nMessage d'erreur : %1%" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:147 msgid "Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp." @@ -1850,7 +1844,7 @@ msgid "CURL init has failed. PrusaSlicer will be unable to establish network con msgstr "L'initialisation de CURL a échoué. PrusaSlicer ne pourra pas établir de connexions réseau. Voir les journaux pour plus de détails." #: src/slic3r/GUI/wxExtensions.cpp:624 -#, c-format +#, possible-c-format msgid "Current mode is %s" msgstr "Le mode actuel est %s" @@ -2086,7 +2080,7 @@ msgid "Delete Object" msgstr "Supprimer l'Objet" #: src/slic3r/GUI/GUI_ObjectSettings.cpp:104 -#, c-format +#, possible-c-format msgid "Delete Option %s" msgstr "Supprimer l'Option %s" @@ -2348,15 +2342,9 @@ msgid "Do not rearrange the given models before merging and keep their original msgstr "Ne pas ré-agencer les modèles donnés avant la fusion et conserver leurs coordonnées XY originales." #: src/slic3r/GUI/Field.cpp:288 -#, c-format -msgid "" -"Do you mean %s%% instead of %s %s?\n" -"Select YES if you want to change this value to %s%%, \n" -"or NO if you are sure that %s %s is a correct value." -msgstr "" -"Voulez vous dire %s%% au lieu de %s%s ?\n" -"Sélectionnez OUI si vous voulez changer cette valeur pour %s%%,\n" -"ou NON si vous êtes certain que %s%s est une valeur correcte." +#, possible-c-format +msgid "Do you mean %s%% instead of %s %s?\nSelect YES if you want to change this value to %s%%, \nor NO if you are sure that %s %s is a correct value." +msgstr "Voulez vous dire %s%% au lieu de %s%s ?\nSélectionnez OUI si vous voulez changer cette valeur pour %s%%,\nou NON si vous êtes certain que %s%s est une valeur correcte." #: src/slic3r/GUI/DoubleSlider.cpp:2138 msgid "Do you want to delete all saved tool changes?" @@ -2394,6 +2382,10 @@ msgstr "Ne pas agencer" msgid "Don't notify about new releases any more" msgstr "Ne plus me notifier au sujet des nouvelles publications" +#: src/slic3r/GUI/Plater.cpp:1431 +msgid "Don't show again" +msgstr "Ne plus afficher" + #: src/libslic3r/PrintConfig.cpp:403 msgid "Don't support bridges" msgstr "Ne pas supporter les ponts" @@ -2463,7 +2455,7 @@ msgstr "Éditer la couleur" #: src/slic3r/GUI/DoubleSlider.cpp:1083 msgid "Edit current color - Right click the colored slider segment" -msgstr "Modifier la couleur actuelle - Cliquez avec le bouton droit sur le segment de curseur de couleur" +msgstr "Modifier la couleur actuelle - Cliquez avec le bouton droit sur le segment coloré de la barre de défilement" #: src/slic3r/GUI/DoubleSlider.cpp:1702 msgid "Edit custom G-code" @@ -2514,7 +2506,7 @@ msgid "Eject SD card / Flash drive after the G-code was exported to it." msgstr "Éjecter la carte SD / la clef USB une fois que la G-code y a été exporté." #: src/slic3r/GUI/Plater.cpp:2034 -#, c-format +#, possible-c-format msgid "Ejecting of device %s(%s) has failed." msgstr "L'éjection de l'appareil %s(%s) a échoué." @@ -2571,6 +2563,14 @@ msgstr "Activer le lissage" msgid "Enable ironing of the top layers with the hot print head for smooth surface" msgstr "Activer le lissage des couches supérieures avec la tête d'impression chaude pour une surface lisse" +#: src/slic3r/GUI/GLCanvas3D.cpp:3901 +msgid "Enable rotations (slow)" +msgstr "Activer les rotations (lentes)" + +#: src/slic3r/GUI/Preferences.cpp:207 +msgid "Enable support for legacy 3DConnexion devices" +msgstr "Active la prise en charge des dispositifs 3DConnexion" + #: src/libslic3r/PrintConfig.cpp:2009 msgid "Enable support material generation." msgstr "Activer la génération des supports." @@ -2671,6 +2671,10 @@ msgstr "Saisissez le nombre de copies :" msgid "Enter the temperature needed for extruding your filament." msgstr "Entrez la température nécessaire pour extruder votre filament." +#: src/libslic3r/PrintConfig.cpp:813 +msgid "Enter weight of the empty filament spool. One may weigh a partially consumed filament spool before printing and one may compare the measured weight with the calculated weight of the filament with the spool to find out whether the amount of filament on the spool is sufficient to finish the print." +msgstr "Entrez le poids d'une bobine de filament vide. De cette manière pouvez peser une bobine de filament partiellement utilisée avant l'impression et ainsi comparer le poids que vous avez mesuré avec le poids du filament calculé avec la bobine pour savoir si la quantité de filament est suffisante pour terminer l'impression." + #: src/libslic3r/PrintConfig.cpp:797 msgid "Enter your filament cost per kg here. This is only for statistical information." msgstr "Entrez le coût par Kg de votre filament. Ceci est uniquement pour l'information statistique." @@ -2697,7 +2701,7 @@ msgid "Error" msgstr "Erreur" #: src/slic3r/GUI/FirmwareDialog.cpp:645 -#, c-format +#, possible-c-format msgid "Error accessing port at %s: %s" msgstr "Erreur d'accès au port sur %s : %s" @@ -2706,12 +2710,12 @@ msgid "Error during reload" msgstr "Erreur lors du rechargement" #: src/slic3r/GUI/Plater.cpp:5172 -#, c-format +#, possible-c-format msgid "Error exporting 3MF file %s" msgstr "Erreur d'export du fichier 3MF %s" #: src/slic3r/GUI/Plater.cpp:5138 -#, c-format +#, possible-c-format msgid "Error exporting AMF file %s" msgstr "Erreur d'export du fichier AMF %s" @@ -2758,7 +2762,7 @@ msgid "ERROR:" msgstr "ERREUR :" #: src/slic3r/GUI/FirmwareDialog.cpp:647 -#, c-format +#, possible-c-format msgid "Error: %s" msgstr "Erreur : %s" @@ -2793,7 +2797,7 @@ msgid "Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm" msgstr "Dépasse de %1%=%2% mm pour être imprimable avec une buse de diamètre %3% mm" #: src/slic3r/GUI/UpdateDialogs.cpp:191 src/slic3r/GUI/UpdateDialogs.cpp:246 -#, c-format +#, possible-c-format msgid "Exit %s" msgstr "Sortir de %s" @@ -3051,7 +3055,7 @@ msgstr "Extrudeur" #: src/slic3r/GUI/DoubleSlider.cpp:1263 src/slic3r/GUI/DoubleSlider.cpp:1297 #: src/slic3r/GUI/GLCanvas3D.cpp:983 src/slic3r/GUI/GUI_ObjectList.cpp:1832 #: src/slic3r/GUI/Tab.cpp:2489 src/libslic3r/GCode/PreviewData.cpp:450 -#, c-format +#, possible-c-format msgid "Extruder %d" msgstr "Extrudeur %d" @@ -3063,6 +3067,10 @@ msgstr "L'extrudeur (outil) est remplacée par l'extrudeur \"%1%\"" msgid "Extruder changed to" msgstr "Extrudeur changé à" +#: src/slic3r/GUI/Tab.cpp:1589 +msgid "Extruder clearance" +msgstr "Dégagement de l'extrudeur" + #: src/slic3r/GUI/Tab.cpp:1563 msgid "Extruder clearance (mm)" msgstr "Dégagement de l'extrudeur (mm)" @@ -3207,6 +3215,10 @@ msgstr "filament" msgid "Filament and Nozzle Diameters" msgstr "Diamètres du Filament et de la Buse" +#: src/slic3r/GUI/Plater.cpp:1189 +msgid "Filament at extruder %1%" +msgstr "Filament de l'extrudeur %1%" + #: src/slic3r/GUI/ConfigWizard.cpp:1349 msgid "Filament Diameter:" msgstr "Diamètre du Filament :" @@ -3311,10 +3323,22 @@ msgstr "échec d'écriture du fichier" msgid "Filename" msgstr "Nom de fichier" +#: src/slic3r/GUI/ConfigWizard.cpp:1181 +msgid "Files association" +msgstr "Association de fichiers" + #: src/libslic3r/PrintConfig.cpp:811 msgid "Fill angle" msgstr "Angle du remplissage" +#: src/slic3r/GUI/Plater.cpp:1651 +msgid "Fill bed" +msgstr "Remplir le plateau" + +#: src/slic3r/GUI/Plater.cpp:3936 +msgid "Fill bed with instances" +msgstr "Remplir le plateau avec des copies" + #: src/libslic3r/PrintConfig.cpp:825 msgid "Fill density" msgstr "Densité de remplissage" @@ -3335,6 +3359,10 @@ msgstr "Motif pour les remplissages de faible densité." msgid "Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells." msgstr "Motif pour les remplissages pour le remplissage du haut. Ceci affecte seulement la couche externe visible en haut, et non les coques solides adjacentes." +#: src/slic3r/GUI/Plater.cpp:3936 +msgid "Fill the remaining area of bed with instances of the selected object" +msgstr "Remplir l'espace restant du plateau avec des copies de l'objet sélectionné" + #: src/slic3r/GUI/BonjourDialog.cpp:225 msgid "Finished" msgstr "Terminé" @@ -3450,26 +3478,16 @@ msgstr "Seulement pour les générateur de supports" #. TRN Description for "WHITE BULLET" #: src/slic3r/GUI/Tab.cpp:3702 -msgid "" -"for the left button: indicates a non-system (or non-default) preset,\n" -"for the right button: indicates that the settings hasn't been modified." -msgstr "" -"pour le bouton gauche : indique un préréglage non-système (ou non par défaut),\n" -"pour le bouton droit : indique que le réglage n'a pas été modifié." +msgid "for the left button: indicates a non-system (or non-default) preset,\nfor the right button: indicates that the settings hasn't been modified." +msgstr "pour le bouton gauche : indique un préréglage non-système (ou non par défaut),\npour le bouton droit : indique que le réglage n'a pas été modifié." #: src/slic3r/GUI/ConfigManipulation.cpp:135 -msgid "" -"For the Wipe Tower to work with the soluble supports, the support layers\n" -"need to be synchronized with the object layers." -msgstr "" -"Pour que la tour de nettoyage fonctionne avec les supports solubles, les couches du support\n" -"doivent être synchronisées avec les couches d'objets." +msgid "For the Wipe Tower to work with the soluble supports, the support layers\nneed to be synchronized with the object layers." +msgstr "Pour que la tour de nettoyage fonctionne avec les supports solubles, les couches du support\ndoivent être synchronisées avec les couches d'objets." #: src/libslic3r/Print.cpp:1422 msgid "For the Wipe Tower to work with the soluble supports, the support layers need to be synchronized with the object layers." -msgstr "" -"Pour que la Tour de Nettoyage fonctionne avec des supports solubles, les couches de support\n" -"doivent être synchronisées avec les couches de l'objet." +msgstr "Pour que la Tour de Nettoyage fonctionne avec des supports solubles, les couches de support\ndoivent être synchronisées avec les couches de l'objet." #: src/libslic3r/PrintConfig.cpp:3028 msgid "Force pad around object everywhere" @@ -3507,17 +3525,17 @@ msgstr "Vue Avant" msgid "full profile name" msgstr "nom de profil complet" +#: src/libslic3r/PrintConfig.cpp:817 +msgid "g" +msgstr "g" + #: src/slic3r/GUI/MainFrame.cpp:1527 msgid "G-code" msgstr "G-code" #: src/slic3r/GUI/DoubleSlider.cpp:1146 -msgid "" -"G-code associated to this tick mark is in a conflict with print mode.\n" -"Editing it will cause changes of Slider data." -msgstr "" -"Le G-code associé à cette coche est en conflit avec le mode d'impression.\n" -"Le modifier entraînera des modifications des données du curseur." +msgid "G-code associated to this tick mark is in a conflict with print mode.\nEditing it will cause changes of Slider data." +msgstr "Le G-code associé à cette coche est en conflit avec le mode d'impression.\nLe modifier entraînera des modifications des données de la Barre de défilement." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:165 msgid "G-code file exported to %1%" @@ -3773,9 +3791,9 @@ msgid "Heights at which a filament change is to occur." msgstr "Hauteurs auxquelles le changement de filament doit se produire." #: src/slic3r/GUI/ConfigWizard.cpp:451 -#, c-format +#, possible-c-format msgid "Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print." -msgstr "Bonjour, bienvenu dans %s ! Ce %s vous aide à la configuration initiale ; juste quelques paramètres et vous serez prêt à imprimer." +msgstr "Bonjour, bienvenue dans %s ! Cet %s est là pour vous aider à effectuer la configuration initiale ; juste quelques paramètres et vous serez prêt à imprimer." #: src/libslic3r/PrintConfig.cpp:3564 msgid "Help" @@ -3793,6 +3811,10 @@ msgstr "Aide (options SLA)" msgid "Here you can adjust required purging volume (mm³) for any given pair of tools." msgstr "Ici vous pouvez ajuster le volume de purge nécessaire (mm³) pour une paire d'outils donnée." +#: src/slic3r/GUI/DoubleSlider.cpp:1849 +msgid "Hide ruler" +msgstr "Cacher la règle" + #: src/libslic3r/PrintConfig.cpp:1017 msgid "High extruder current on filament swap" msgstr "Courant de l'extrudeur élevé lors du changement de filament" @@ -3858,6 +3880,42 @@ msgstr "Nid d'abeille" msgid "Horizontal shells" msgstr "Coques horizontales" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:209 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:213 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:238 +msgid "Horizontal slider - Move active thumb Left" +msgstr "Barre de défilement horizontale - Déplacer le curseur actif vers la Gauche" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:210 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:214 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:239 +msgid "Horizontal slider - Move active thumb Right" +msgstr "Barre de défilement horizontale - Déplacer le curseur actif vers la Droite" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:238 +msgid "Horizontal slider - Move current thumb Left" +msgstr "Barre de défilement horizontale - Déplacer le curseur actuel vers la Gauche" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:239 +msgid "Horizontal slider - Move current thumb Right" +msgstr "Barre de défilement horizontale - Déplacer vers la droite actuelle" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:236 +msgid "Horizontal slider - Set left thumb as active" +msgstr "Barre de défilement horizontale - Définir le curseur de gauche comme actif" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:236 +msgid "Horizontal slider - Set left thumb to current thumb" +msgstr "Barre de défilement horizontale - Régler le curseur de gauche sur le curseur actuel" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:237 +msgid "Horizontal slider - Set right thumb as active" +msgstr "Barre de défilement horizontale - Définir le curseur de droite comme actif" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:237 +msgid "Horizontal slider - Set right thumb to current thumb" +msgstr "Barre de défilement horizontale - Régler le curseur de droite sur le curseur actuel" + #: src/libslic3r/PrintConfig.cpp:279 msgid "Horizontal width of the brim that will be printed around each object on the first layer." msgstr "Largeur horizontale de la bordure qui sera imprimée autour de chaque objet sur la première couche." @@ -3879,12 +3937,8 @@ msgid "Hostname, IP or URL" msgstr "Nom d'hôte, IP ou URL" #: src/slic3r/GUI/Tab.cpp:210 -msgid "" -"Hover the cursor over buttons to find more information \n" -"or click this button." -msgstr "" -"Passez le curseur au dessus des boutons pour obtenir plus d'informations\n" -"ou cliquez sur ce bouton." +msgid "Hover the cursor over buttons to find more information \nor click this button." +msgstr "Passez le curseur au dessus des boutons pour obtenir plus d'informations\nou cliquez sur ce bouton." #: src/libslic3r/PrintConfig.cpp:2976 msgid "How far should the pad extend around the contained geometry" @@ -3910,6 +3964,10 @@ msgstr "Comment appliquer des limites" msgid "How to apply the Machine Limits" msgstr "Comment appliquer les limites de la machine" +#: src/libslic3r/PrintConfig.cpp:163 +msgid "HTTP digest" +msgstr "HTTP digest" + #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:358 #: src/libslic3r/PrintConfig.cpp:113 msgid "HTTPS CA File" @@ -3932,12 +3990,12 @@ msgid "If checked, supports will be generated automatically based on the overhan msgstr "Si cette case est cochée, les supports seront générés automatiquement en fonction de la valeur seuil de surplomb. Si cette case n'est pas cochée, les supports seront générés uniquement dans les volumes \"Générateur de supports\"." #: src/slic3r/GUI/ConfigWizard.cpp:1132 -#, c-format +#, possible-c-format msgid "If enabled, %s checks for new application versions online. When a new version becomes available, a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done." msgstr "Si activé, %s vérifie en ligne l'existence de nouvelles versions de Slic3r PE. Lorsqu'une nouvelle version est disponible, une notification est affichée au démarrage suivant de l'application (jamais pendant l'utilisation du programme). Ceci est uniquement un mécanisme de notification, aucune installation automatique n'est faite." #: src/slic3r/GUI/ConfigWizard.cpp:1142 -#, c-format +#, possible-c-format msgid "If enabled, %s downloads updates of built-in system presets in the background.These updates are downloaded into a separate temporary location.When a new preset version becomes available it is offered at application startup." msgstr "Si activé, %s télécharge les mises à jours des préréglages système intégrés en arrière-plan. Ces mises à jour sont téléchargées dans un répertoire temporaire séparé. Lorsqu'une nouvelle version de préréglages est disponible, elle est proposée au démarrage de l'application." @@ -3946,12 +4004,8 @@ msgid "If enabled, all printing extruders will be primed at the front edge of th msgstr "Si ceci est activé, tous les extrudeurs qui impriment seront positionnés sur la bordure avant du lit d'impression au début de l'impression." #: src/slic3r/GUI/ConfigWizard.cpp:1164 -msgid "" -"If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" -"If not enabled, the Reload from disk command will ask to select each file using an open file dialog." -msgstr "" -"Si activé, permet à la commande Recharger à partir du disque de rechercher et de charger automatiquement les fichiers lorsqu'elle est invoquée.\n" -"Si non activée, la commande Recharger à partir du disque demandera de sélectionner chaque fichier à l'aide d'une boîte de dialogue d'ouverture de fichier." +msgid "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\nIf not enabled, the Reload from disk command will ask to select each file using an open file dialog." +msgstr "Si activé, permet à la commande Recharger à partir du disque de rechercher et de charger automatiquement les fichiers lorsqu'elle est invoquée.\nSi non activée, la commande Recharger à partir du disque demandera de sélectionner chaque fichier à l'aide d'une boîte de dialogue d'ouverture de fichier." #: src/slic3r/GUI/Preferences.cpp:91 msgid "If enabled, allows the Reload from disk command to automatically find and load the files when invoked." @@ -3959,7 +4013,7 @@ msgstr "Si activé, permet à la commande Recharger à partir du disque de reche #: src/slic3r/GUI/Preferences.cpp:238 msgid "If enabled, changes made using the sequential slider, in preview, apply only to gcode top layer. If disabled, changes made using the sequential slider, in preview, apply to the whole gcode." -msgstr "Si activé, les modifications apportées à l'aide du curseur séquentiel, dans l'aperçu, s'appliquent uniquement à la couche supérieure du gcode. Si désactivé, les modifications effectuées à l'aide du curseur séquentiel, dans l'aperçu, s'appliquent à l'ensemble du gcode." +msgstr "Si activé, les modifications apportées à l'aide de la barre de défilement séquentielle, dans l'aperçu, s'appliquent uniquement à la couche supérieure du gcode. Si désactivé, les modifications effectuées à l'aide de la barre de défilement séquentielle, dans l'aperçu, s'appliquent à l'ensemble du gcode." #: src/slic3r/GUI/Preferences.cpp:83 msgid "If enabled, PrusaSlicer will check for the new versions of itself online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done." @@ -3973,6 +4027,18 @@ msgstr "Si activé, rend l'objet à l'aide de la carte d'environnement." msgid "If enabled, reverses the direction of zoom with mouse wheel" msgstr "Si activé, inverse la direction du zoom avec la molette de la souris" +#: src/slic3r/GUI/Preferences.cpp:93 +msgid "If enabled, sets PrusaSlicer as default application to open .3mf files." +msgstr "Si activé, définit PrusaSlicer comme application par défaut pour ouvrir les fichiers .3mf." + +#: src/slic3r/GUI/Preferences.cpp:100 +msgid "If enabled, sets PrusaSlicer as default application to open .stl files." +msgstr "Si activé, définit PrusaSlicer comme l'application par défaut pour ouvrir les fichiers .stl." + +#: src/slic3r/GUI/Preferences.cpp:179 +msgid "If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files." +msgstr "Si activé, définit la Visualisation de G-code de PrusaSlicer comme application par défaut pour ouvrir les fichiers .gcode." + #: src/slic3r/GUI/Preferences.cpp:99 msgid "If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup." msgstr "Si activé, Slic3r télécharge les mises à jours des préréglages système intégrés en arrière-plan. Ces mises à jour sont téléchargées dans un répertoire temporaire séparé. Lorsqu'une nouvelle version de préréglages est disponible, elle est proposée au démarrage de l'application." @@ -3989,6 +4055,14 @@ msgstr "Si activé, le bouton de réduction de la barre latérale apparaîtra da msgid "If enabled, the command line arguments are sent to an existing instance of GUI PrusaSlicer, or an existing PrusaSlicer window is activated. Overrides the \"single_instance\" configuration value from application preferences." msgstr "Si activé, les arguments de ligne de commande sont envoyés à une instance existante de la GUI de PrusaSlicer, ou une fenêtre existante de PrusaSlicer est activée. Remplace la valeur de configuration \"single_instance\" des préférences de l'application." +#: src/slic3r/GUI/Preferences.cpp:278 +msgid "If enabled, the descriptions of configuration parameters in settings tabs woldn't work as hyperlinks. If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks." +msgstr "Si activé, les descriptions des paramètres de configuration dans les onglets de réglage ne fonctionneront pas comme hyperliens. Si désactivé, les descriptions des paramètres de configuration dans les onglets de réglage fonctionneront comme hyperliens." + +#: src/slic3r/GUI/Preferences.cpp:209 +msgid "If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M" +msgstr "Si activé, la boite de dialogue pour configurer les dispositifs 3DConnexion devient accessible en appuyant sur CTRL+M" + #: src/libslic3r/PrintConfig.cpp:1804 msgid "If enabled, the skirt will be as tall as a highest printed object. This is useful to protect an ABS or ASA print from warping and detaching from print bed due to wind draft." msgstr "Si elle est activée, la jupe sera aussi haute que l'objet imprimé le plus haut. Cela sert à protéger les impressions ABS ou ASA des phénomènes de déformation ou de décollement du plateau d'impression liés au flux d'air." @@ -4102,10 +4176,18 @@ msgstr "Importer la Configuration depuis le &projet" msgid "Import Config from ini/amf/3mf/gcode" msgstr "Importer une Configuration depuis ini/amf/3mf/gcode" +#: src/slic3r/GUI/Plater.cpp:1419 +msgid "Import config only" +msgstr "Importer la configuration uniquement" + #: src/slic3r/GUI/Jobs/SLAImportJob.cpp:39 msgid "Import file" msgstr "Importer le fichier" +#: src/slic3r/GUI/Plater.cpp:1418 +msgid "Import geometry only" +msgstr "Importer la géométrie uniquement" + #: src/slic3r/GUI/Jobs/SLAImportJob.cpp:46 msgid "Import model and profile" msgstr "Importer le modèle et le profil" @@ -4170,7 +4252,7 @@ msgid "in" msgstr "in" #: src/slic3r/GUI/GUI_ObjectList.cpp:3885 -#, c-format +#, possible-c-format msgid "In this mode you can select only other %s Items%s" msgstr "Dans ce mode vous ne pouvez sélectionner que d'autres %s Items %s" @@ -4183,7 +4265,7 @@ msgid "Incompatible presets" msgstr "Préréglages incompatibles" #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:75 -#, c-format +#, possible-c-format msgid "Incompatible with this %s" msgstr "Incompatible avec ce %s" @@ -4197,12 +4279,8 @@ msgstr "Augmenter/diminuer la zone d'édition" #. TRN Description for "UNLOCKED LOCK" #: src/slic3r/GUI/Tab.cpp:3695 -msgid "" -"indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\n" -"Click the UNLOCKED LOCK icon to reset all settings for current option group to the system (or default) values." -msgstr "" -"indique que certains paramètres ont été modifiés et ne sont pas égaux aux valeurs du système (ou par défaut) pour le groupe d'options actuel.\n" -"Cliquez sur l'icône CADENAS OUVERT pour régler tous les paramètres pour le groupe d'options actuel sur les valeurs du système (ou par défaut)." +msgid "indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\nClick the UNLOCKED LOCK icon to reset all settings for current option group to the system (or default) values." +msgstr "indique que certains paramètres ont été modifiés et ne sont pas égaux aux valeurs du système (ou par défaut) pour le groupe d'options actuel.\nCliquez sur l'icône CADENAS OUVERT pour régler tous les paramètres pour le groupe d'options actuel sur les valeurs du système (ou par défaut)." #. TRN Description for "LOCKED LOCK" #: src/slic3r/GUI/Tab.cpp:3691 @@ -4211,12 +4289,8 @@ msgstr "indique que les paramètres sont les mêmes que les valeurs système (ou #. TRN Description for "BACK ARROW" #: src/slic3r/GUI/Tab.cpp:3707 -msgid "" -"indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" -"Click the BACK ARROW icon to reset all settings for the current option group to the last saved preset." -msgstr "" -"indique que les paramètres ont été changés et qu'ils ne sont pas identiques au dernier préréglage enregistré du groupe d'options en cours.\n" -"Cliquez sur l'icône FLÈCHE ARRIÈRE pour restaurer tous les paramètres du groupe d'options en cours avec les valeurs du dernier préréglage enregistré." +msgid "indicates that the settings were changed and are not equal to the last saved preset for the current option group.\nClick the BACK ARROW icon to reset all settings for the current option group to the last saved preset." +msgstr "indique que les paramètres ont été changés et qu'ils ne sont pas identiques au dernier préréglage enregistré du groupe d'options en cours.\nCliquez sur l'icône FLÈCHE ARRIÈRE pour restaurer tous les paramètres du groupe d'options en cours avec les valeurs du dernier préréglage enregistré." #: src/slic3r/GUI/ConfigManipulation.cpp:210 #: src/slic3r/GUI/GUI_ObjectList.cpp:35 src/slic3r/GUI/GUI_ObjectList.cpp:93 @@ -4291,7 +4365,7 @@ msgstr "Inspecter / activer les instantanés de configuration" #: src/slic3r/GUI/ObjectDataViewModel.cpp:62 #: src/slic3r/GUI/ObjectDataViewModel.cpp:218 -#, c-format +#, possible-c-format msgid "Instance %d" msgstr "Instance %d" @@ -4437,11 +4511,31 @@ msgstr "Gigue" msgid "Jump to height" msgstr "Sauter à la hauteur" +#: src/slic3r/GUI/DoubleSlider.cpp:1223 +#, possible-c-format +msgid "Jump to height %s\nor Set ruler mode" +msgstr "Sauter à la hauteur %s\nou Définir le mode règle" + +#: src/slic3r/GUI/DoubleSlider.cpp:1220 +#, possible-c-format +msgid "Jump to height %s\nSet ruler mode\nor Set extruder sequence for the entire print" +msgstr "Sauter à la hauteur %s\nDéfinir le mode règle\nou Définir la séquence d'extrusion pour toute l'impression" + #: src/slic3r/GUI/DoubleSlider.cpp:1075 -#, c-format +#, possible-c-format msgid "Jump to height %s or Set extruder sequence for the entire print" msgstr "Sauter à la hauteur %s ou Définir la séquence d'extrusion pour toute l'impression" +#: src/slic3r/GUI/DoubleSlider.cpp:1222 +#, possible-c-format +msgid "Jump to height %s or Set ruler mode" +msgstr "Sauter à la hauteur %s ou Définir le mode règle" + +#: src/slic3r/GUI/DoubleSlider.cpp:1220 +#, possible-c-format +msgid "Jump to height %s Set ruler mode\n or Set extruder sequence for the entire print" +msgstr "Sauter à la hauteur %s Définir le mode règle\nou Définir la séquence d'extrusion pour toute l'impression" + #: src/slic3r/GUI/DoubleSlider.cpp:1071 src/slic3r/GUI/DoubleSlider.cpp:1852 msgid "Jump to move" msgstr "Sauter pour bouger" @@ -4617,6 +4711,10 @@ msgstr "Longueur" msgid "Length of the cooling tube to limit space for cooling moves inside it." msgstr "Longueur du tube de refroidissement pour limiter l'espace pour les déplacements de refroidissement à l'intérieur de celui-ci." +#: src/libslic3r/PrintConfig.cpp:1068 +msgid "Length of the infill anchor" +msgstr "Longueur de l'ancre de remplissage" + #. TRN "Slic3r _is licensed under the_ License" #: src/slic3r/GUI/AboutDialog.cpp:141 msgid "License agreements of all following programs (libraries) are part of application license agreement" @@ -4670,6 +4768,14 @@ msgstr "Charger la configuration depuis le fichier spécifié. Ceci peut être u msgid "Load exported configuration file" msgstr "Charger le fichier de configuration exporté" +#: src/slic3r/GUI/Plater.cpp:1543 src/slic3r/GUI/Plater.cpp:4976 +msgid "Load File" +msgstr "Charger le Fichier" + +#: src/slic3r/GUI/Plater.cpp:1548 src/slic3r/GUI/Plater.cpp:4981 +msgid "Load Files" +msgstr "Charger les Fichiers" + #: src/slic3r/GUI/GUI_ObjectList.cpp:2038 msgid "Load Part" msgstr "Charger une Partie" @@ -4765,6 +4871,10 @@ msgstr "Boucles (minimum)" msgid "Lower Layer" msgstr "Couche Inférieure" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:219 +msgid "Lower layer" +msgstr "Couche Inférieure" + #: src/slic3r/GUI/Tab.cpp:2346 src/slic3r/GUI/Tab.cpp:2442 #: src/libslic3r/PrintConfig.cpp:1202 src/libslic3r/PrintConfig.cpp:1237 #: src/libslic3r/PrintConfig.cpp:1254 src/libslic3r/PrintConfig.cpp:1271 @@ -4997,6 +5107,10 @@ msgstr "Mouvement brusque maximum Y" msgid "Maximum jerk Z" msgstr "Mouvement brusque maximum Z" +#: src/libslic3r/PrintConfig.cpp:1095 +msgid "Maximum length of the infill anchor" +msgstr "Longueur maximum de l'ancre de remplissage" + #: src/libslic3r/PrintConfig.cpp:2814 msgid "Maximum number of bridges that can be placed on a pillar. Bridges hold support point pinheads and connect to pillars as small branches." msgstr "Le nombre de ponts maximum pouvant être placés sur un pilier. Les ponts soutiennent les têtes des points de support et sont connectés aux piliers comme de petites branches." @@ -5172,7 +5286,7 @@ msgid "Mirror vertically" msgstr "Symétriser verticalement" #: src/slic3r/Utils/AstroBox.cpp:69 src/slic3r/Utils/OctoPrint.cpp:68 -#, c-format +#, possible-c-format msgid "Mismatched type of print host: %s" msgstr "Mauvais appariement de l'hôte d'impression : %s" @@ -5372,30 +5486,48 @@ msgstr "Roulette de la souris:" msgid "Move" msgstr "Déplacer" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:255 +msgid "Move active slider thumb Left" +msgstr "Déplacer la barre de défilement active vers la Gauche" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:256 +msgid "Move active slider thumb Right" +msgstr "Déplacer la barre de défilement active vers la Droite" + #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1219 msgid "Move clipping plane" msgstr "Déplacer le plan de coupe" #: src/slic3r/GUI/KBShortcutsDialog.cpp:216 msgid "Move current slider thumb Down" -msgstr "Déplacer le curseur actuel vers le bas" +msgstr "Déplacer la barre de défilement actuelle vers le Bas" #: src/slic3r/GUI/KBShortcutsDialog.cpp:229 msgid "Move current slider thumb Left" -msgstr "Déplacer le curseur actuel vers la gauche" +msgstr "Déplacer la barre de défilement actuelle vers la Gauche" #: src/slic3r/GUI/KBShortcutsDialog.cpp:230 msgid "Move current slider thumb Right" -msgstr "Déplacer le curseur actuel vers la droite" +msgstr "Déplacer la barre de défilement actuelle vers la Droite" #: src/slic3r/GUI/KBShortcutsDialog.cpp:215 msgid "Move current slider thumb Up" -msgstr "Déplacer le curseur actuel vers le haut" +msgstr "Déplacer la barre de défilement actuelle vers le Haut" #: src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp:835 msgid "Move drainage hole" msgstr "Déplacer le trou de drainage" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:209 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:213 +msgid "Move horizontal slider current thumb Left" +msgstr "Déplacer la Barre de défilement horizontale vers la Gauche actuelle" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:210 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:214 +msgid "Move horizontal slider current thumb Right" +msgstr "Déplacer la barre de défilement horizontale vers la Droite actuelle" + #: src/slic3r/GUI/GLCanvas3D.cpp:3810 msgid "Move Object" msgstr "Déplacer l'Objet" @@ -5424,6 +5556,16 @@ msgstr "Déplacer la sélection de 10 mm dans la direction positive Y" msgid "Move support point" msgstr "Déplacer un point de support" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:208 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:212 +msgid "Move vertical slider current thumb Down" +msgstr "Déplacer la barre de défilement verticale vers le Bas actuel" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:207 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:211 +msgid "Move vertical slider current thumb Up" +msgstr "Déplacer la barre de défilement verticale vers le Haut actuel" + #: src/slic3r/GUI/GCodeViewer.cpp:2492 msgid "Movement" msgstr "Mouvement" @@ -5445,7 +5587,7 @@ msgid "Multi-part object detected" msgstr "Objet multi-pièces détecté" #: src/slic3r/GUI/FirmwareDialog.cpp:419 src/slic3r/GUI/FirmwareDialog.cpp:454 -#, c-format +#, possible-c-format msgid "Multiple %s devices found. Please only connect one at a time for flashing." msgstr "Plusieurs %s équipements ont été détectés. Veuillez n'en connecter qu'un seul à la fois pour le processus de flash." @@ -5454,14 +5596,8 @@ msgid "Multiple Extruders" msgstr "Extrudeurs Multiples" #: src/slic3r/GUI/Plater.cpp:2372 -msgid "" -"Multiple objects were loaded for a multi-material printer.\n" -"Instead of considering them as multiple objects, should I consider\n" -"these files to represent a single object having multiple parts?" -msgstr "" -"Plusieurs fichiers ont été chargés pour une impression multi-matériaux.\n" -"Au lieu de les considérer en tant que plusieurs objets, dois-je considérer\n" -"ces fichiers en tant que un seul objet ayant plusieurs pièces ?" +msgid "Multiple objects were loaded for a multi-material printer.\nInstead of considering them as multiple objects, should I consider\nthese files to represent a single object having multiple parts?" +msgstr "Plusieurs fichiers ont été chargés pour une impression multi-matériaux.\nAu lieu de les considérer en tant que plusieurs objets, dois-je considérer\nces fichiers en tant que un seul objet ayant plusieurs pièces ?" #: src/libslic3r/PrintConfig.cpp:3638 msgid "Multiply copies by creating a grid." @@ -5530,7 +5666,7 @@ msgid "New version is available." msgstr "Une nouvelle version est disponible." #: src/slic3r/GUI/UpdateDialogs.cpp:38 -#, c-format +#, possible-c-format msgid "New version of %s is available" msgstr "Une nouvelle version de %s est disponible" @@ -5613,14 +5749,8 @@ msgid "Note, that this/those printer(s) will be deleted after deleting of the se msgstr "Notez que cette/ces imprimante(s) seront supprimées après la suppression du préréglage sélectionné." #: src/slic3r/GUI/Tab.cpp:2039 -msgid "" -"Note: All parameters from this group are moved to the Physical Printer settings (see changelog).\n" -"\n" -"A new Physical Printer profile is created by clicking on the \"cog\" icon right of the Printer profiles combo box, by selecting the \"Add physical printer\" item in the Printer combo box. The Physical Printer profile editor opens also when clicking on the \"cog\" icon in the Printer settings tab. The Physical Printer profiles are being stored into PrusaSlicer/physical_printer directory." -msgstr "" -"Remarque : Tous les paramètres de ce groupe sont déplacés vers les paramètres de l'imprimante physique (voir le journal des modifications).\n" -"\n" -"Un nouveau profil d'imprimante physique est créé en cliquant sur l'icône \"rouage\" à droite de la zone de liste déroulante de Profils d'imprimante, en sélectionnant l'élément \"Ajouter une imprimante physique\" dans la zone de liste déroulante Imprimante. L'éditeur de profil d'imprimante physique s'ouvre également en cliquant sur l'icône \"rouage\" dans l'onglet Réglages de l'imprimante. Les profils d'imprimante physique sont stockés dans le répertoire PrusaSlicer/physical_printer." +msgid "Note: All parameters from this group are moved to the Physical Printer settings (see changelog).\n\nA new Physical Printer profile is created by clicking on the \"cog\" icon right of the Printer profiles combo box, by selecting the \"Add physical printer\" item in the Printer combo box. The Physical Printer profile editor opens also when clicking on the \"cog\" icon in the Printer settings tab. The Physical Printer profiles are being stored into PrusaSlicer/physical_printer directory." +msgstr "Remarque : Tous les paramètres de ce groupe sont déplacés vers les paramètres de l'imprimante physique (voir le journal des modifications).\n\nUn nouveau profil d'imprimante physique est créé en cliquant sur l'icône \"rouage\" à droite de la zone de liste déroulante de Profils d'imprimante, en sélectionnant l'élément \"Ajouter une imprimante physique\" dans la zone de liste déroulante Imprimante. L'éditeur de profil d'imprimante physique s'ouvre également en cliquant sur l'icône \"rouage\" dans l'onglet Réglages de l'imprimante. Les profils d'imprimante physique sont stockés dans le répertoire PrusaSlicer/physical_printer." #: src/slic3r/Utils/AstroBox.cpp:92 msgid "Note: AstroBox version at least 1.1.0 is required." @@ -5809,10 +5939,14 @@ msgid "On OSX there is always only one instance of app running by default. Howev msgstr "Sur OSX, il n'y a toujours qu'une seule instance d'application en cours d'exécution par défaut. Cependant, il est permis d'exécuter plusieurs instances de la même application à partir de la ligne de commande. Dans ce cas, ces paramètres n'autoriseront qu'une seule instance." #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:359 -#, c-format +#, possible-c-format msgid "On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain." msgstr "Dans ce système, %s utilise des certificats HTTPS issus du système Magasin de Certificats ou Trousseau." +#: src/slic3r/GUI/KBShortcutsDialog.cpp:215 +msgid "On/Off one layer mode of the vertical slider" +msgstr "On/Off mode couche unique de la barre de défilement verticale" + #: src/slic3r/GUI/DoubleSlider.cpp:1064 msgid "One layer mode" msgstr "Mode couche unique" @@ -5847,7 +5981,7 @@ msgstr "Rétracter uniquement lors du franchissement de périmètres" #: src/slic3r/GUI/ConfigWizard.cpp:714 msgid "Only the following installed printers are compatible with the selected filament:" -msgstr "Seules les imprimantes installées suivantes sont compatibles avec le filament sélectionné :" +msgstr "Seules les imprimantes installées qui suivent sont compatibles avec le filament sélectionné :" #: src/slic3r/GUI/Tab.cpp:1517 msgid "Ooze prevention" @@ -5874,6 +6008,10 @@ msgstr "Ouvrir une nouvelle instance de PrusaSlicer" msgid "Open a project file" msgstr "Ouvrir un fichier de projet" +#: src/slic3r/GUI/Plater.cpp:1417 +msgid "Open as project" +msgstr "Ouvrir en tant que projet" + #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:330 msgid "Open CA certificate file" msgstr "Ouvrir le fichier de certificat CA" @@ -5916,7 +6054,7 @@ msgid "Open PrusaSlicer" msgstr "Ouvrir PrusaSlicer" #: src/slic3r/GUI/MainFrame.cpp:918 src/slic3r/GUI/MainFrame.cpp:1317 -#, c-format +#, possible-c-format msgid "Open the %s website in your browser" msgstr "Ouvrir le site web de %s dans votre navigateur" @@ -6218,7 +6356,7 @@ msgid "Physical printers" msgstr "Imprimantes physiques" #: src/slic3r/GUI/ConfigWizard.cpp:1226 -#, c-format +#, possible-c-format msgid "Pick another vendor supported by %s" msgstr "Choisissez un autre fournisseur pris en charge par %s" @@ -6350,6 +6488,10 @@ msgstr "Le préréglage \"%1%\" n’est pas compatible avec le nouveau profil d msgid "Preset with name \"%1%\" already exists and is imcopatible with selected printer." msgstr "Le préréglage avec le nom \"%1%\" existe déjà et est incompatible avec l'imprimante sélectionnée." +#: src/slic3r/GUI/SavePresetDialog.cpp:136 +msgid "Preset with name \"%1%\" already exists and is incopatible with selected printer." +msgstr "Un préréglage nommé \"%1%\" existe déjà et il est incompatible avec l'imprimante sélectionnée." + #: src/slic3r/GUI/SavePresetDialog.cpp:148 msgid "Preset with name \"%1%\" already exists." msgstr "Un préréglage avec le nom \"%1%\" existe déjà." @@ -6368,23 +6510,15 @@ msgid "Press to activate selection rectangle" msgstr "Appuyer pour activer le rectangle de sélection" #: src/slic3r/GUI/KBShortcutsDialog.cpp:155 -msgid "" -"Press to select multiple objects\n" -"or move multiple objects with mouse" -msgstr "" -"Clicquez pour sélectionner plusieurs objets\n" -"ou pour déplacer plusieurs objets avec la souris" +msgid "Press to select multiple objects\nor move multiple objects with mouse" +msgstr "Clicquez pour sélectionner plusieurs objets\nou pour déplacer plusieurs objets avec la souris" #: src/slic3r/GUI/KBShortcutsDialog.cpp:221 #: src/slic3r/GUI/KBShortcutsDialog.cpp:222 #: src/slic3r/GUI/KBShortcutsDialog.cpp:231 #: src/slic3r/GUI/KBShortcutsDialog.cpp:232 -msgid "" -"Press to speed up 5 times while moving thumb\n" -"with arrow keys or mouse wheel" -msgstr "" -"Appuyez pour accélérer 5 fois tout en déplaçant le pouce\n" -"avec les touches fléchées ou la molette de la souris" +msgid "Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel" +msgstr "Appuyez pour accélérer 5 fois tout en déplaçant le pouce\navec les touches fléchées ou la molette de la souris" #: src/slic3r/GUI/KBShortcutsDialog.cpp:212 src/slic3r/GUI/Plater.cpp:4052 #: src/slic3r/GUI/Tab.cpp:2559 @@ -6539,7 +6673,7 @@ msgstr "Impression avec plusieurs extrudeurs de différents diamètres de buse. #. TRN "Processing input_file_basename" #: src/slic3r/GUI/MainFrame.cpp:1550 -#, c-format +#, possible-c-format msgid "Processing %s" msgstr "Traitement %s" @@ -6594,13 +6728,9 @@ msgid "PrusaSlicer is closing: Unsaved Changes" msgstr "PrusaSlicer se ferme : modifications non enregistrées" #: src/slic3r/GUI/OpenGLManager.cpp:259 -#, c-format -msgid "" -"PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" -"while OpenGL version %s, render %s, vendor %s was detected." -msgstr "" -"PrusaSlicer a besoin de pilotes graphiques opérationnels OpenGL 2.0 pour fonctionner correctement,\n" -"alors que OpenGL version %s, rendu %s, fournisseur %s a été détecté." +#, possible-c-format +msgid "PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \nwhile OpenGL version %s, render %s, vendor %s was detected." +msgstr "PrusaSlicer a besoin de pilotes graphiques opérationnels OpenGL 2.0 pour fonctionner correctement,\nalors que OpenGL version %s, rendu %s, fournisseur %s a été détecté." #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:50 msgid "PrusaSlicer version" @@ -6611,14 +6741,8 @@ msgid "PrusaSlicer will remember your action." msgstr "PrusaSlicer se souviendra de votre action." #: src/slic3r/GUI/ConfigWizard.cpp:1174 -msgid "" -"PrusaSlicer's user interfaces comes in three variants:\n" -"Simple, Advanced, and Expert.\n" -"The Simple mode shows only the most frequently used settings relevant for regular 3D printing. The other two offer progressively more sophisticated fine-tuning, they are suitable for advanced and expert users, respectively." -msgstr "" -"Les interfaces utilisateur de PrusaSlicer se déclinent en trois variantes :\n" -"Simple, Avancé et Expert.\n" -"Le mode Simple affiche uniquement les paramètres les plus fréquemment utilisés pertinents pour l'impression 3D régulière. Les deux autres offrent des réglages fins de plus en plus sophistiqués, ils conviennent respectivement aux utilisateurs avancés et experts." +msgid "PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\nThe Simple mode shows only the most frequently used settings relevant for regular 3D printing. The other two offer progressively more sophisticated fine-tuning, they are suitable for advanced and expert users, respectively." +msgstr "Les interfaces utilisateur de PrusaSlicer se déclinent en trois variantes :\nSimple, Avancé et Expert.\nLe mode Simple affiche uniquement les paramètres les plus fréquemment utilisés pertinents pour l'impression 3D régulière. Les deux autres offrent des réglages fins de plus en plus sophistiqués, ils conviennent respectivement aux utilisateurs avancés et experts." #: src/slic3r/GUI/UnsavedChangesDialog.cpp:668 msgid "PrusaSlicer: Don't ask me again" @@ -6664,7 +6788,7 @@ msgstr "Rapide" #: src/slic3r/GUI/GUI_ObjectList.cpp:1661 #: src/slic3r/GUI/GUI_ObjectList.cpp:1667 #: src/slic3r/GUI/GUI_ObjectList.cpp:2008 -#, c-format +#, possible-c-format msgid "Quick Add Settings (%s)" msgstr "Ajout de Réglages Rapide (%s)" @@ -6677,7 +6801,7 @@ msgid "Quick Slice and Save As" msgstr "Découpage Rapide et Enregistrer Sous" #: src/slic3r/GUI/MainFrame.cpp:1144 src/slic3r/GUI/MainFrame.cpp:1402 -#, c-format +#, possible-c-format msgid "Quit %s" msgstr "Quitter %s" @@ -6702,14 +6826,8 @@ msgid "Ramming customization" msgstr "Personnalisation de l'expulsion" #: src/slic3r/GUI/WipeTowerDialog.cpp:41 -msgid "" -"Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n" -"\n" -"This is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." -msgstr "" -"L'Expulsion décrit l'extrusion rapide qui a lieu juste avant un changement d'outil sur une imprimante MM à extrudeur unique. Le but est de donner une forme correcte au filament déchargé afin qu'il n'empêche pas l'insertion du nouveau filament et puisse être réinséré lui-même plus tard. Cette phase est importante et des matériaux différents peuvent nécessiter des vitesses d'extrusion différentes pour obtenir la bonne forme. De ce fait, les débits d'extrusion pendant l'expulsion sont ajustables.\n" -"\n" -"Ceci est un paramétrage de niveau expert, et un mauvais ajustement provoquera probablement des blocages, des accrochages de la roue de l'extrudeur sur le filament , etc ..." +msgid "Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." +msgstr "L'Expulsion décrit l'extrusion rapide qui a lieu juste avant un changement d'outil sur une imprimante MM à extrudeur unique. Le but est de donner une forme correcte au filament déchargé afin qu'il n'empêche pas l'insertion du nouveau filament et puisse être réinséré lui-même plus tard. Cette phase est importante et des matériaux différents peuvent nécessiter des vitesses d'extrusion différentes pour obtenir la bonne forme. De ce fait, les débits d'extrusion pendant l'expulsion sont ajustables.\n\nCeci est un paramétrage de niveau expert, et un mauvais ajustement provoquera probablement des blocages, des accrochages de la roue de l'extrudeur sur le filament , etc ..." #: src/slic3r/GUI/WipeTowerDialog.cpp:91 msgid "Ramming line spacing" @@ -6769,7 +6887,7 @@ msgid "Recent projects" msgstr "Proj&ets récents" #: src/slic3r/GUI/PresetHints.cpp:262 -#, c-format +#, possible-c-format msgid "Recommended object thin wall thickness for layer height %.2f and" msgstr "Épaisseur des parois fines de l'objet recommandée pour la hauteur de couche %.2f et" @@ -6804,7 +6922,7 @@ msgid "Redo" msgstr "Recommencer" #: src/slic3r/GUI/GLCanvas3D.cpp:4382 -#, c-format +#, possible-c-format msgid "Redo %1$d Action" msgid_plural "Redo %1$d Actions" msgstr[0] "Répéter %1$d Action" @@ -7028,22 +7146,22 @@ msgid "Report an I&ssue" msgstr "S&ignaler un Problème" #: src/slic3r/GUI/MainFrame.cpp:928 src/slic3r/GUI/MainFrame.cpp:1327 -#, c-format +#, possible-c-format msgid "Report an issue on %s" msgstr "Signaler un problème sur %s" #: src/slic3r/Utils/PresetUpdater.cpp:733 -#, c-format +#, possible-c-format msgid "requires max. %s" msgstr "nécessite max. %s" #: src/slic3r/Utils/PresetUpdater.cpp:730 -#, c-format +#, possible-c-format msgid "requires min. %s" msgstr "nécessite min. %s" #: src/slic3r/Utils/PresetUpdater.cpp:726 -#, c-format +#, possible-c-format msgid "requires min. %s and max. %s" msgstr "nécessite min. %s et max. %s" @@ -7227,8 +7345,12 @@ msgstr "Angle de rotation autour de l'axe Y en degrés." msgid "Rotation angle around the Z axis in degrees." msgstr "Angle de rotation autour de l'axe Z en degrés." +#: src/slic3r/GUI/DoubleSlider.cpp:1836 +msgid "Ruler mode" +msgstr "Mode règle" + #: src/slic3r/GUI/GUI_App.cpp:1474 -#, c-format +#, possible-c-format msgid "Run %s" msgstr "Run %s" @@ -7260,12 +7382,12 @@ msgid "Save" msgstr "Enregistrer" #: src/slic3r/GUI/SavePresetDialog.cpp:72 -#, c-format +#, possible-c-format msgid "Save %s as:" msgstr "Enregistrer %s sous :" #: src/slic3r/GUI/MainFrame.cpp:1527 -#, c-format +#, possible-c-format msgid "Save %s file as:" msgstr "Enregistrer le fichier %s sous :" @@ -7287,7 +7409,7 @@ msgstr "Enregistrer la configuration dans le fichier spécifié." #. TRN "Save current Settings" #: src/slic3r/GUI/Tab.cpp:203 -#, c-format +#, possible-c-format msgid "Save current %s" msgstr "Enregistrer l'état actuel %s" @@ -7463,6 +7585,10 @@ msgstr "Sélectionner tous les points" msgid "Select all standard printers" msgstr "Sélectionner toutes les imprimantes standard" +#: src/slic3r/GUI/Plater.cpp:1422 +msgid "Select an action to apply to the file" +msgstr "Sélectionnez une action à appliquer au fichier" + #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1215 msgid "Select by rectangle" msgstr "Sélectionner par rectangle" @@ -7532,14 +7658,8 @@ msgid "Select what kind of support do you need" msgstr "Choisissez le type de support dont vous avez besoin" #: src/slic3r/GUI/DoubleSlider.cpp:2135 -msgid "" -"Select YES if you want to delete all saved tool changes, \n" -"NO if you want all tool changes switch to color changes, \n" -"or CANCEL to leave it unchanged." -msgstr "" -"Sélectionnez OUI si vous souhaitez supprimer tous les changements d'outil enregistrées, \n" -"NON si vous souhaitez que tous les changements d'outil soient remplacés par des modifications de couleur, \n" -"ou ANNULER pour ne pas les modifier." +msgid "Select YES if you want to delete all saved tool changes, \nNO if you want all tool changes switch to color changes, \nor CANCEL to leave it unchanged." +msgstr "Sélectionnez OUI si vous souhaitez supprimer tous les changements d'outil enregistrées, \nNON si vous souhaitez que tous les changements d'outil soient remplacés par des modifications de couleur, \nou ANNULER pour ne pas les modifier." #: src/slic3r/GUI/Selection.cpp:191 msgid "Selection-Add" @@ -7619,11 +7739,11 @@ msgstr "Impression séquentielle" #: src/slic3r/GUI/KBShortcutsDialog.cpp:235 msgid "Sequential Slider" -msgstr "Curseur séquentiel" +msgstr "Barre de défilement séquentielle" #: src/slic3r/GUI/Preferences.cpp:230 msgid "Sequential slider applied only to top layer" -msgstr "Curseur séquentiel appliqué uniquement à la couche supérieur" +msgstr "Barre de défilement séquentielle appliquée uniquement à la couche supérieure" #: src/slic3r/GUI/FirmwareDialog.cpp:807 msgid "Serial port:" @@ -7668,7 +7788,7 @@ msgstr "Définir la séquence d'extrudeur (outil)" #: src/slic3r/GUI/KBShortcutsDialog.cpp:218 msgid "Set lower thumb to current slider thumb" -msgstr "Définir le curseur inférieur sur le curseur actuel" +msgstr "Définir le curseur inférieur sur la position actuelle de la barre de défilement" #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:305 msgid "Set Mirror" @@ -7679,7 +7799,7 @@ msgid "Set number of instances" msgstr "Définir le nombre d'instances" #: src/slic3r/GUI/Plater.cpp:4860 -#, c-format +#, possible-c-format msgid "Set numbers of copies to %d" msgstr "Régler le nombre de copies sur %d" @@ -7699,6 +7819,10 @@ msgstr "Définir Imprimable" msgid "Set Printable Instance" msgstr "Définir une Instance Imprimable" +#: src/slic3r/GUI/DoubleSlider.cpp:1836 +msgid "Set ruler mode" +msgstr "Définir le mode règle" + #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:893 msgid "Set Scale" msgstr "Définir l'Échelle" @@ -7765,15 +7889,11 @@ msgstr "Définir une Instance non-Imprimable" #: src/slic3r/GUI/KBShortcutsDialog.cpp:217 msgid "Set upper thumb to current slider thumb" -msgstr "Définir le curseur supérieur sur le curseur actuel" +msgstr "Régler le curseur supérieur sur la position actuelle de la barre de défilement" #: src/libslic3r/PrintConfig.cpp:3714 -msgid "" -"Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\n" -"For example. loglevel=2 logs fatal, error and warning level messages." -msgstr "" -"Définit la sensibilité de journalisation. 0 : fatal, 1: erreur, 2 : avertissement, 3 : info, 4 : débogage, 5 : trace\n" -"Par exemple. loglevel = 2 enregistre les messages d'erreur et d'avertissement de niveau fatal." +msgid "Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\nFor example. loglevel=2 logs fatal, error and warning level messages." +msgstr "Définit la sensibilité de journalisation. 0 : fatal, 1: erreur, 2 : avertissement, 3 : info, 4 : débogage, 5 : trace\nPar exemple. loglevel = 2 enregistre les messages d'erreur et d'avertissement de niveau fatal." #: src/slic3r/GUI/BedShapeDialog.cpp:292 src/slic3r/GUI/MainFrame.cpp:1969 msgid "Settings" @@ -7850,10 +7970,22 @@ msgstr "Afficher la boîte de dialogue à propos" msgid "Show advanced settings" msgstr "Afficher les réglages avancés" +#: src/slic3r/GUI/Preferences.cpp:120 +msgid "Show drop project dialog" +msgstr "Afficher la boite de dialogue pour déposer un projet" + #: src/slic3r/GUI/PrintHostDialogs.cpp:157 msgid "Show error message" msgstr "Afficher le message d'erreur" +#: src/slic3r/GUI/DoubleSlider.cpp:1832 +msgid "Show estimated print time" +msgstr "Afficher le temps d'impression estimé" + +#: src/slic3r/GUI/DoubleSlider.cpp:1832 +msgid "Show estimated print time on the ruler" +msgstr "Afficher sur la règle le temps d'impression estimé" + #: src/slic3r/GUI/Preferences.cpp:112 msgid "Show incompatible print and filament presets" msgstr "Afficher les préréglages d'impression et de filament incompatibles" @@ -7866,6 +7998,14 @@ msgstr "Afficher la liste des raccourcis clavier" msgid "Show normal mode" msgstr "Afficher le mode normal" +#: src/slic3r/GUI/DoubleSlider.cpp:1828 +msgid "Show object height" +msgstr "Afficher la hauteur de l'objet" + +#: src/slic3r/GUI/DoubleSlider.cpp:1828 +msgid "Show object height on the ruler" +msgstr "Afficher la hauteur de l'objet sur la règle" + #: src/slic3r/GUI/MainFrame.cpp:1294 msgid "Show object/instance labels in 3D scene" msgstr "Afficher les labels de l'objet /instance dans la scène 3D" @@ -7942,10 +8082,18 @@ msgstr "Afficher le répertoire de configuration utilisateur (datadir)" msgid "Show/Hide 3Dconnexion devices settings dialog" msgstr "Afficher/Masquer le dialogue des paramètres des périphériques 3Dconnexion" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:175 +msgid "Show/Hide 3Dconnexion devices settings dialog, if enabled" +msgstr "Afficher/Cacher la boite de dialogue des réglages des appareils 3Dconnexion, si activé" + #: src/slic3r/GUI/KBShortcutsDialog.cpp:216 msgid "Show/Hide Legend & Estimated printing time" msgstr "Afficher/Masquer la légende et le temps d'impression estimé" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:216 +msgid "Show/Hide Legend and Estimated printing time" +msgstr "Afficher/Cacher la Légende et le Temps d'impression estimé" + #: src/slic3r/GUI/KBShortcutsDialog.cpp:141 msgid "Show/Hide object/instance labels" msgstr "Afficher/Masquer les labels de l'objet/instance" @@ -7971,15 +8119,8 @@ msgid "Single Extruder Multi Material" msgstr "Multi Material à extrudeur unique" #: src/slic3r/GUI/Tab.cpp:2101 -msgid "" -"Single Extruder Multi Material is selected, \n" -"and all extruders must have the same diameter.\n" -"Do you want to change the diameter for all extruders to first extruder nozzle diameter value?" -msgstr "" -"Le Multi-Matériaux Extrudeur Unique est sélectionné,\n" -"et tous les extrudeurs doivent avoir le même diamètre.\n" -"Voulez-vous modifier le diamètre pour tous les extrudeurs\n" -"en utilisant la valeur du diamètre de la buse du premier extrudeur ?" +msgid "Single Extruder Multi Material is selected, \nand all extruders must have the same diameter.\nDo you want to change the diameter for all extruders to first extruder nozzle diameter value?" +msgstr "Le Multi-Matériaux Extrudeur Unique est sélectionné,\net tous les extrudeurs doivent avoir le même diamètre.\nVoulez-vous modifier le diamètre pour tous les extrudeurs\nen utilisant la valeur du diamètre de la buse du premier extrudeur ?" #: src/slic3r/GUI/Tab.cpp:2476 msgid "Single extruder multimaterial parameters" @@ -8088,6 +8229,10 @@ msgstr "Slic3r peut envoyer des fichiers G-code à un hôte d'impression. Ce cha msgid "Slic3r can upload G-code files to a printer host. This field should contain the hostname, IP address or URL of the printer host instance." msgstr "Slic3r peut télécharger des fichiers G-code vers un hôte d'impression. Ce champ doit contenir le nom d'hôte, l'adresse IP ou l'URL de l'instance hôte d'impression." +#: src/libslic3r/PrintConfig.cpp:100 +msgid "Slic3r can upload G-code files to a printer host. This field should contain the hostname, IP address or URL of the printer host instance. Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL in the following format: https://username:password@your-octopi-address/" +msgstr "Slic3r peut télécharger des fichiers G-code vers un hôte d'impression. Ce champ doit contenir le nom de l'hôte, l'adresse IP ou l'URL d'instance de l'hôte d'impression. Vous pouvez accéder à un hôte d'impression se trouvant derrière HAProxy avec basic auth activé en mettant le nom d'utilisateur et le mot de passe dans l'URL en respectant le format suivant : https://username:password@your-octopi-address/" + #: src/libslic3r/PrintConfig.cpp:1407 msgid "Slic3r will not scale speed down below this speed." msgstr "Slic3r ne descendra pas en-dessous de cette vitesse." @@ -8198,7 +8343,7 @@ msgstr "Nom de l'instantané" #: src/slic3r/GUI/MainFrame.cpp:911 src/slic3r/GUI/MainFrame.cpp:1310 msgid "Software &Releases" -msgstr "Softwa&re Publications" +msgstr "Publications du Softwa&re " #: src/slic3r/GUI/PresetHints.cpp:183 msgid "solid infill" @@ -8243,13 +8388,9 @@ msgid "Some G/M-code commands, including temperature control and others, are not msgstr "Certaines commandes G/M-code, y compris le contrôle de la température ainsi que d'autres, ne sont pas universelles. Paramétrez cette option dans le firmware de votre imprimante pour obtenir une sortie compatible. L'option \"Pas d'extrusion\" empêche complètement PrusaSlicer d'exporter toute valeur d'extrusion." #: src/slic3r/GUI/Plater.cpp:2309 -#, c-format -msgid "" -"Some object(s) in file %s looks like saved in inches.\n" -"Should I consider them as a saved in inches and convert them?" -msgstr "" -"Certains objets du fichier %s semblent être enregistrés en pouces.\n" -"Dois-je les considérer comme enregistrés en pouces et les convertir ?" +#, possible-c-format +msgid "Some object(s) in file %s looks like saved in inches.\nShould I consider them as a saved in inches and convert them?" +msgstr "Certains objets du fichier %s semblent être enregistrés en pouces.\nDois-je les considérer comme enregistrés en pouces et les convertir ?" #: src/slic3r/GUI/GLCanvas3D.cpp:636 msgid "Some objects are not visible." @@ -8271,6 +8412,10 @@ msgstr "Certains objets peuvent s'accommoder de quelques petits socles au lieu d msgid "Some printers or printer setups may have difficulties printing with a variable layer height. Enabled by default." msgstr "Certaines imprimantes ou certains réglages d'imprimante peuvent rencontrer des difficultés pour imprimer avec une hauteur de couche variable. Activé par défaut." +#: src/slic3r/GUI/GLCanvas3D.cpp:3967 +msgid "Spacing" +msgstr "Espacement" + #: src/libslic3r/PrintConfig.cpp:2126 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "Espacement entre les lignes d'interface. Mettez à zéro pour obtenir une interface solide." @@ -8416,6 +8561,10 @@ msgstr "Scinder en pièces" msgid "Split to Parts" msgstr "Scinder en Pièces" +#: src/libslic3r/PrintConfig.cpp:812 +msgid "Spool weight" +msgstr "Poids de la bobine" + #: src/slic3r/GUI/ConfigWizard.cpp:307 msgid "Standard" msgstr "Standard" @@ -8450,28 +8599,8 @@ msgid "Start the application" msgstr "Démarrer l'application" #: src/slic3r/GUI/GUI_App.cpp:386 -msgid "" -"Starting with %1% 2.3, configuration directory on Linux has changed (according to XDG Base Directory Specification) to \n" -"%2%.\n" -"\n" -"This directory did not exist yet (maybe you run the new version for the first time).\n" -"However, an old %1% configuration directory was detected in \n" -"%3%.\n" -"\n" -"Consider moving the contents of the old directory to the new location in order to access your profiles, etc.\n" -"Note that if you decide to downgrade %1% in future, it will use the old location again.\n" -"\n" -"What do you want to do now?" -msgstr "" -"À partir de %1% 2.3, le répertoire de configuration sous Linux a été modifié (selon la XDG Base Directory Specification) en %2%.\n" -"\n" -"Ce répertoire n'existait pas encore (peut-être que vous exécutez la nouvelle version pour la première fois).\n" -"Cependant, un ancien répertoire de configuration %1% a été détecté dans %3%.\n" -"\n" -"Envisagez de déplacer le contenu de l'ancien répertoire vers le nouvel emplacement afin d'accéder à vos profils, etc.\n" -"Notez que si vous décidez de rétrograder %1% à l'avenir, il utilisera à nouveau l'ancien emplacement.\n" -"\n" -"Que voulez-vous faire maintenant ?" +msgid "Starting with %1% 2.3, configuration directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\nThis directory did not exist yet (maybe you run the new version for the first time).\nHowever, an old %1% configuration directory was detected in \n%3%.\n\nConsider moving the contents of the old directory to the new location in order to access your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old location again.\n\nWhat do you want to do now?" +msgstr "À partir de %1% 2.3, le répertoire de configuration sous Linux a été modifié (selon la XDG Base Directory Specification) en %2%.\n\nCe répertoire n'existait pas encore (peut-être que vous exécutez la nouvelle version pour la première fois).\nCependant, un ancien répertoire de configuration %1% a été détecté dans %3%.\n\nEnvisagez de déplacer le contenu de l'ancien répertoire vers le nouvel emplacement afin d'accéder à vos profils, etc.\nNotez que si vous décidez de rétrograder %1% à l'avenir, il utilisera à nouveau l'ancien emplacement.\n\nQue voulez-vous faire maintenant ?" #: src/slic3r/GUI/PrintHostDialogs.cpp:149 msgid "Status" @@ -8494,7 +8623,7 @@ msgid "Stealth mode" msgstr "Mode furtif" #: src/slic3r/GUI/Plater.cpp:5118 -#, c-format +#, possible-c-format msgid "STL file exported to %s" msgstr "Fichier STL exporté vers %s" @@ -8507,7 +8636,7 @@ msgid "Success!" msgstr "Réussi !" #: src/slic3r/GUI/Plater.cpp:2047 -#, c-format +#, possible-c-format msgid "Successfully unmounted. The device %s(%s) can now be safely removed from the computer." msgstr "Démonté avec succès. Le périphérique %s(% s) peut maintenant être retiré en toute sécurité de l'ordinateur." @@ -8635,12 +8764,12 @@ msgid "Supports stealth mode" msgstr "Supporte le mode silencieux" #: src/slic3r/GUI/ConfigManipulation.cpp:158 -msgid "" -"Supports work better, if the following feature is enabled:\n" -"- Detect bridging perimeters" -msgstr "" -"Les supports fonctionnent mieux, si la fonctionnalité suivante est activée :\n" -"- Détecter les périmètres de pontage" +msgid "Supports work better, if the following feature is enabled:\n- Detect bridging perimeters" +msgstr "Les supports fonctionnent mieux, si la fonctionnalité suivante est activée :\n- Détecter les périmètres de pontage" + +#: src/slic3r/GUI/DoubleSlider.cpp:1824 +msgid "Supprese show the ruler" +msgstr "Supprimer l'affichage de la règle" #: src/slic3r/GUI/Preferences.cpp:104 msgid "Suppress \" - default - \" presets" @@ -8650,6 +8779,10 @@ msgstr "Supprimer les préréglages \" - par défaut - \"" msgid "Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available." msgstr "Supprimer les préréglages \" - par défaut - \" dans les choix Impression / Filament / Imprimante une fois qu'il y a d'autres préréglages valides disponibles." +#: src/slic3r/GUI/Preferences.cpp:276 +msgid "Suppress to open hyperlink in browser" +msgstr "Supprimer pour ouvrir l'hyperlien dans le navigateur" + #: src/slic3r/GUI/MainFrame.cpp:1527 msgid "SVG" msgstr "SVG" @@ -8687,7 +8820,7 @@ msgid "Switch to Settings" msgstr "Basculer dans le Réglages" #: src/slic3r/GUI/wxExtensions.cpp:623 -#, c-format +#, possible-c-format msgid "Switch to the %s mode" msgstr "Basculer vers le mode %s" @@ -8696,20 +8829,12 @@ msgid "Switching Presets: Unsaved Changes" msgstr "Changement de préréglages : modifications non enregistrées" #: src/slic3r/GUI/GUI_App.cpp:1608 -msgid "" -"Switching the language will trigger application restart.\n" -"You will lose content of the plater." +msgid "Switching the language will trigger application restart.\nYou will lose content of the plater." msgstr "Le changement de langue déclenchera le redémarrage de l’application. L'objet et tous les paramètres non enregistrés seront perdus." #: src/slic3r/GUI/WipeTowerDialog.cpp:365 -msgid "" -"Switching to simple settings will discard changes done in the advanced mode!\n" -"\n" -"Do you want to proceed?" -msgstr "" -"Basculer vers les réglages simples annulera les changements effectués en mode avancé !\n" -"\n" -"Voulez-vous continuer ?" +msgid "Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?" +msgstr "Basculer vers les réglages simples annulera les changements effectués en mode avancé !\n\nVoulez-vous continuer ?" #: src/slic3r/GUI/Tab.cpp:1332 msgid "symbolic profile name" @@ -8781,28 +8906,21 @@ msgid "The %1% infill pattern is not supposed to work at 100%% density." msgstr "Le modèle de remplissage %1% n'est pas censé fonctionner avec une densité de 100%%." #: src/slic3r/GUI/FirmwareDialog.cpp:548 -#, c-format +#, possible-c-format msgid "The %s device could not have been found" msgstr "L'équipement %s n'a pas pu être trouvé" #: src/slic3r/GUI/FirmwareDialog.cpp:436 -#, c-format -msgid "" -"The %s device was not found.\n" -"If the device is connected, please press the Reset button next to the USB connector ..." -msgstr "" -"L'équipement %s n'a pas été trouvé.\n" -"Si l'équipement est connecté, veuillez appuyer sur le bouton Reset à côté du connecteur USB ..." +#, possible-c-format +msgid "The %s device was not found.\nIf the device is connected, please press the Reset button next to the USB connector ..." +msgstr "L'équipement %s n'a pas été trouvé.\nSi l'équipement est connecté, veuillez appuyer sur le bouton Reset à côté du connecteur USB ..." #: src/slic3r/GUI/Tab.cpp:1238 msgid "The current custom preset will be detached from the parent system preset." msgstr "Le préréglage personnalisé actuel sera détaché du préréglage système parent." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:925 -msgid "" -"The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" -"Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n" -"once the rotation is embedded into the object coordinates." +msgid "The currently manipulated object is tilted (rotation angles are not multiples of 90°).\nNon-uniform scaling of tilted objects is only possible in the World coordinate system,\nonce the rotation is embedded into the object coordinates." msgstr "L'objet actuel est incliné (les angles de rotation ne sont pas des multiples de 90 °). La mise à l'échelle non uniforme des objets inclinés est possible dans le système de coordonnées seulement quand la rotation est incorporée aux coordonnées de l'objet." #: src/libslic3r/PrintConfig.cpp:2890 @@ -8936,14 +9054,8 @@ msgid "The percentage of smaller pillars compared to the normal pillar diameter msgstr "Le pourcentage de piliers plus petits par rapport au diamètre de pilier normal qui sont utilisés dans les zones problématiques où un pilier normal ne peut pas rentrer." #: src/libslic3r/PrintConfig.cpp:2567 -msgid "" -"The percentage of the bed area. \n" -"If the print area exceeds the specified value, \n" -"then a slow tilt will be used, otherwise - a fast tilt" -msgstr "" -"Pourcentage de la zone du lit.\n" -"Si la zone d'impression excède la valeur spécifiée,\n" -"alors une inclinaison lente sera appliquée, sinon - une inclinaison rapide" +msgid "The percentage of the bed area. \nIf the print area exceeds the specified value, \nthen a slow tilt will be used, otherwise - a fast tilt" +msgstr "Pourcentage de la zone du lit.\nSi la zone d'impression excède la valeur spécifiée,\nalors une inclinaison lente sera appliquée, sinon - une inclinaison rapide" #: src/slic3r/GUI/Tab.cpp:3430 msgid "The physical printer(s) below is based on the preset, you are going to delete." @@ -8990,22 +9102,12 @@ msgid "The selected object couldn't be split because it contains only one part." msgstr "L'objet sélectionné n'a pu être scindé car il ne contient qu'une seule pièce." #: src/slic3r/GUI/MainFrame.cpp:1003 -msgid "" -"The selected project is no longer available.\n" -"Do you want to remove it from the recent projects list?" -msgstr "" -"Le projet sélectionné n'est plus disponible.\n" -"Voulez-vous le retirer de la liste des projets récents?" +msgid "The selected project is no longer available.\nDo you want to remove it from the recent projects list?" +msgstr "Le projet sélectionné n'est plus disponible.\nVoulez-vous le retirer de la liste des projets récents?" #: src/slic3r/GUI/DoubleSlider.cpp:1121 -msgid "" -"The sequential print is on.\n" -"It's impossible to apply any custom G-code for objects printing sequentually.\n" -"This code won't be processed during G-code generation." -msgstr "" -"L'impression séquentielle est activée.\n" -"Il est impossible d'appliquer un G-code personnalisé pour des objets en impression séquentielle.\n" -"Ce code ne sera pas traité au cours de la génération du G-code." +msgid "The sequential print is on.\nIt's impossible to apply any custom G-code for objects printing sequentually.\nThis code won't be processed during G-code generation." +msgstr "L'impression séquentielle est activée.\nIl est impossible d'appliquer un G-code personnalisé pour des objets en impression séquentielle.\nCe code ne sera pas traité au cours de la génération du G-code." #: src/slic3r/GUI/ConfigWizard.cpp:1187 msgid "The size of the object can be specified in inches" @@ -9024,23 +9126,9 @@ msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "La vitesse des rétractations (ne s'applique qu'au moteur de l'extrudeur)." #: src/slic3r/GUI/ConfigManipulation.cpp:80 -#, c-format -msgid "" -"The Spiral Vase mode requires:\n" -"- one perimeter\n" -"- no top solid layers\n" -"- 0% fill density\n" -"- no support material\n" -"- Ensure vertical shell thickness enabled\n" -"- Detect thin walls disabled" -msgstr "" -"Les prérequis du mode Vase Spiral sont :\n" -"-Un seul périmètre\n" -"-Aucunes couches solides supérieures\n" -"-Une densité de remplissage de 0%\n" -"-Pas de support\n" -"-Vérifier que l'épaisseur de coque verticale est activée\n" -"-La détection de parois fines doit être désactivée" +#, possible-c-format +msgid "The Spiral Vase mode requires:\n- one perimeter\n- no top solid layers\n- 0% fill density\n- no support material\n- Ensure vertical shell thickness enabled\n- Detect thin walls disabled" +msgstr "Les prérequis du mode Vase Spiral sont :\n-Un seul périmètre\n-Aucunes couches solides supérieures\n-Une densité de remplissage de 0%\n-Pas de support\n-Vérifier que l'épaisseur de coque verticale est activée\n-La détection de parois fines doit être désactivée" #: src/libslic3r/Print.cpp:1263 msgid "The Spiral Vase option can only be used when printing a single object." @@ -9078,28 +9166,16 @@ msgid "The vertical distance between object and support material interface. Sett msgstr "Distance verticale entre l'objet et l'intercalaire du support. Régler cette valeur sur zéro empêchera Slic3r d'utiliser la vitesse et le débit des ponts pour la première couche de l'objet." #: src/slic3r/GUI/Tab.cpp:2731 -msgid "" -"The Wipe option is not available when using the Firmware Retraction mode.\n" -"\n" -"Shall I disable it in order to enable Firmware Retraction?" -msgstr "" -"L'option Nettoyage n'est pas disponible lorsque vous utilisez le mode Rétractation du Firmware.\n" -"\n" -"Voulez-vous que je la désactive pour permettre la Rétractation du Firmware ?" +msgid "The Wipe option is not available when using the Firmware Retraction mode.\n\nShall I disable it in order to enable Firmware Retraction?" +msgstr "L'option Nettoyage n'est pas disponible lorsque vous utilisez le mode Rétractation du Firmware.\n\nVoulez-vous que je la désactive pour permettre la Rétractation du Firmware ?" #: src/libslic3r/Print.cpp:1294 msgid "The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)." msgstr "À l'heure actuelle, la Tour de Nettoyage ne prend pas en charge l'E volumétrique (use_volumetric_e-0)." #: src/slic3r/GUI/ConfigManipulation.cpp:114 -msgid "" -"The Wipe Tower currently supports the non-soluble supports only\n" -"if they are printed with the current extruder without triggering a tool change.\n" -"(both support_material_extruder and support_material_interface_extruder need to be set to 0)." -msgstr "" -"La tour de nettoyage prend actuellement en charge les supports non solubles seulement\n" -"si ils sont imprimés avec l'extrudeur actuel sans déclencher un changement d'outil.\n" -"(support_material_extruder et support_material_interface_extruder doivent être réglés sur 0)." +msgid "The Wipe Tower currently supports the non-soluble supports only\nif they are printed with the current extruder without triggering a tool change.\n(both support_material_extruder and support_material_interface_extruder need to be set to 0)." +msgstr "La tour de nettoyage prend actuellement en charge les supports non solubles seulement\nsi ils sont imprimés avec l'extrudeur actuel sans déclencher un changement d'outil.\n(support_material_extruder et support_material_interface_extruder doivent être réglés sur 0)." #: src/libslic3r/Print.cpp:1426 msgid "The Wipe Tower currently supports the non-soluble supports only if they are printed with the current extruder without triggering a tool change. (both support_material_extruder and support_material_interface_extruder need to be set to 0)." @@ -9150,45 +9226,29 @@ msgid "There are unprintable objects. Try to adjust support settings to make the msgstr "Il y a des objets non imprimables. Essayez d'ajuster les paramètres de support pour rendre les objets imprimables." #: src/slic3r/GUI/DoubleSlider.cpp:1155 -msgid "" -"There is a color change for extruder that has not been used before.\n" -"Check your settings to avoid redundant color changes." -msgstr "" -"Il y a un changement de couleur pour un extrudeur qui n'a pas été utilisé auparavant.\n" -"Vérifiez vos paramètres pour éviter les changements de couleur redondants." +msgid "There is a color change for extruder that has not been used before.\nCheck your settings to avoid redundant color changes." +msgstr "Il y a un changement de couleur pour un extrudeur qui n'a pas été utilisé auparavant.\nVérifiez vos paramètres pour éviter les changements de couleur redondants." #: src/slic3r/GUI/DoubleSlider.cpp:1149 -msgid "" -"There is a color change for extruder that won't be used till the end of print job.\n" -"This code won't be processed during G-code generation." -msgstr "" -"Il y a un changement de couleur pour un extrudeur qui ne sera pas utilisé avant la fin du travail d'impression.\n" -"Ce code ne sera pas traité lors de la génération du G-code." +msgid "There is a color change for extruder that won't be used till the end of print job.\nThis code won't be processed during G-code generation." +msgstr "Il y a un changement de couleur pour un extrudeur qui ne sera pas utilisé avant la fin du travail d'impression.\nCe code ne sera pas traité lors de la génération du G-code." #: src/slic3r/GUI/DoubleSlider.cpp:1152 -msgid "" -"There is an extruder change set to the same extruder.\n" -"This code won't be processed during G-code generation." -msgstr "" -"Une modification d'extrudeur est défini sur le même extrudeur.\n" -"Ce code ne sera pas traité lors de la génération du G-code." +msgid "There is an extruder change set to the same extruder.\nThis code won't be processed during G-code generation." +msgstr "Une modification d'extrudeur est défini sur le même extrudeur.\nCe code ne sera pas traité lors de la génération du G-code." #: src/libslic3r/GCode.cpp:604 msgid "There is an object with no extrusions on the first layer." msgstr "Il y a un objet sans extrusion sur la première couche." #: src/slic3r/GUI/UpdateDialogs.cpp:225 -#, c-format +#, possible-c-format msgid "This %s version: %s" msgstr "Version de ce %s : %s" #: src/slic3r/GUI/Tab.cpp:1244 -msgid "" -"This action is not revertable.\n" -"Do you want to proceed?" -msgstr "" -"Cette action n'est pas réversible.\n" -"Voulez-vous continuer ?" +msgid "This action is not revertable.\nDo you want to proceed?" +msgstr "Cette action n'est pas réversible.\nVoulez-vous continuer ?" #: src/libslic3r/PrintConfig.cpp:199 msgid "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want." @@ -9263,30 +9323,13 @@ msgid "This file cannot be loaded in a simple mode. Do you want to switch to an msgstr "Ce fichier ne peut être chargé en mode simple. Voulez-vous basculer en mode avancé ?" #: src/slic3r/GUI/Plater.cpp:2319 -msgid "" -"This file contains several objects positioned at multiple heights.\n" -"Instead of considering them as multiple objects, should I consider\n" -"this file as a single object having multiple parts?" -msgstr "" -"Ce fichier contient plusieurs objets positionnés à différentes hauteurs. Au lieu de les considérer comme des objets distincts, voulez-vous que je considère\n" -"ce fichier comme un seul objet en plusieurs parties?" +msgid "This file contains several objects positioned at multiple heights.\nInstead of considering them as multiple objects, should I consider\nthis file as a single object having multiple parts?" +msgstr "Ce fichier contient plusieurs objets positionnés à différentes hauteurs. Au lieu de les considérer comme des objets distincts, voulez-vous que je considère\nce fichier comme un seul objet en plusieurs parties?" #: src/slic3r/GUI/FirmwareDialog.cpp:332 -#, c-format -msgid "" -"This firmware hex file does not match the printer model.\n" -"The hex file is intended for: %s\n" -"Printer reported: %s\n" -"\n" -"Do you want to continue and flash this hex file anyway?\n" -"Please only continue if you are sure this is the right thing to do." -msgstr "" -"Le fichier hex de ce firmware ne correspond pas au modèle d'imprimante.\n" -"Le fichier hex est prévu pour : %s\n" -"Imprimante détectée : %s\n" -"\n" -"Voulez-vous continuer et flasher ce fichier hex quand même ?\n" -"S'il vous plait, ne continuez que si vous êtes certain de faire le bon choix." +#, possible-c-format +msgid "This firmware hex file does not match the printer model.\nThe hex file is intended for: %s\nPrinter reported: %s\n\nDo you want to continue and flash this hex file anyway?\nPlease only continue if you are sure this is the right thing to do." +msgstr "Le fichier hex de ce firmware ne correspond pas au modèle d'imprimante.\nLe fichier hex est prévu pour : %s\nImprimante détectée : %s\n\nVoulez-vous continuer et flasher ce fichier hex quand même ?\nS'il vous plait, ne continuez que si vous êtes certain de faire le bon choix." #: src/libslic3r/PrintConfig.cpp:348 msgid "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time." @@ -9356,12 +9399,16 @@ msgstr "Il s'agit de l'accélération que votre imprimante utilisera pour le rem msgid "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters." msgstr "L'accélération que votre imprimante utilisera pour les périmètres. Une valeur élevée comme 9000 donne généralement de bons résultats si votre matériel le permet. Régler sur zéro afin de désactiver le contrôle de l'accélération pour les périmètres." +#: src/libslic3r/PrintConfig.cpp:1582 +msgid "This is the acceleration your printer will use for perimeters. Set zero to disable acceleration control for perimeters." +msgstr "L'accélération qui sera utilisée par votre imprimante pour les périmètres. Régler sur zéro pour désactiver l'accélération pour les périmètres." + #: src/libslic3r/PrintConfig.cpp:1435 msgid "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "Il s'agit du diamètre de la buse de votre extrudeur (par exemple: 0.5, 0.35, etc.)" #: src/libslic3r/PrintConfig.cpp:1335 -#, c-format +#, possible-c-format msgid "This is the highest printable layer height for this extruder, used to cap the variable layer height and support layer height. Maximum recommended layer height is 75% of the extrusion width to achieve reasonable inter-layer adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." msgstr "Ceci est la hauteur de couche imprimable maximum pour cet extrudeur, utilisée pour plafonner la hauteur de couche variable et la hauteur de couche des supports. La hauteur de couche maximum recommandée est 75% de la largeur d'extrusion afin d'obtenir une adhésion inter-couches correcte. Si réglée sur 0, la hauteur de couche est limitée à 75% du diamètre de la buse." @@ -9378,12 +9425,8 @@ msgid "This matrix describes volumes (in cubic milimetres) required to purge the msgstr "Cette matrice décrit les volumes (en millimètres cube) nécessaires pour purger le nouveau filament dans la tour de nettoyage pour une paire d'outils donnée." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:928 -msgid "" -"This operation is irreversible.\n" -"Do you want to proceed?" -msgstr "" -"Cette opération est irréversible.\n" -"Voulez-vous continuer?" +msgid "This operation is irreversible.\nDo you want to proceed?" +msgstr "Cette opération est irréversible.\nVoulez-vous continuer?" #: src/libslic3r/PrintConfig.cpp:1550 msgid "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled." @@ -9450,17 +9493,9 @@ msgid "This vector saves required volumes to change from/to each tool used on th msgstr "Ce vecteur enregistre les volumes requis pour changer l'outil utilisé pour la tour de nettoyage. Ces valeurs sont utilisées pour simplifier la création des volumes de purge complets ci-dessous." #: src/slic3r/GUI/UpdateDialogs.cpp:216 -#, c-format -msgid "" -"This version of %s is not compatible with currently installed configuration bundles.\n" -"This probably happened as a result of running an older %s after using a newer one.\n" -"\n" -"You may either exit %s and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s." -msgstr "" -"Cette version de %s n'est pas compatible avec les ensembles de configuration actuellement installés.\n" -"Cela survient probablement du fait d'avoir lancé une ancienne version de %s après en avoir utilisé une nouvelle.\n" -"\n" -"Vous pouvez soit quitter %s et essayer à nouveau avec une version plus récente, ou vous pouvez relancer la configuration initiale. Procéder ainsi permettra de créer une sauvegarde de la configuration existante avant d'installer les fichiers compatibles avec ce %s." +#, possible-c-format +msgid "This version of %s is not compatible with currently installed configuration bundles.\nThis probably happened as a result of running an older %s after using a newer one.\n\nYou may either exit %s and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s." +msgstr "Cette version de %s n'est pas compatible avec les ensembles de configuration actuellement installés.\nCela survient probablement du fait d'avoir lancé une ancienne version de %s après en avoir utilisé une nouvelle.\n\nVous pouvez soit quitter %s et essayer à nouveau avec une version plus récente, ou vous pouvez relancer la configuration initiale. Procéder ainsi permettra de créer une sauvegarde de la configuration existante avant d'installer les fichiers compatibles avec ce %s." #: src/libslic3r/PrintConfig.cpp:2601 msgid "This will apply a gamma correction to the rasterized 2D polygons. A gamma value of zero means thresholding with the threshold in the middle. This behaviour eliminates antialiasing without losing holes in polygons." @@ -9536,10 +9571,14 @@ msgid "To use a custom CA file, please import your CA file into Certificate Stor msgstr "Pour utiliser un fichier CA personnalisé, veuillez importer votre fichier CA dans le Magasin de Certificats / Trousseau." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:271 -#, c-format +#, possible-c-format msgid "Toggle %c axis mirroring" msgstr "Activer la symétrie sur l'axe %c" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:215 +msgid "Toggle vertical slider one layer mode ON/OFF" +msgstr "Basculer le mode couche unique de la barre de défilement verticale sur ON/OFF" + #: src/libslic3r/miniz_extension.cpp:93 msgid "too many files" msgstr "trop de fichiers" @@ -9620,6 +9659,10 @@ msgstr "Couches supérieures solides" msgid "Top View" msgstr "Vue du Dessus" +#: src/libslic3r/PrintConfig.cpp:1211 +msgid "Topmost surface only" +msgstr "Uniquement la partie supérieure de la surface" + #: src/slic3r/GUI/WipeTowerDialog.cpp:285 msgid "Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded." msgstr "Le volume de purge total est calculé en additionnant les deux valeurs ci-dessous, en fonction des outils qui sont chargés/déchargés." @@ -9680,13 +9723,9 @@ msgid "Type:" msgstr "Type :" #: src/slic3r/GUI/OpenGLManager.cpp:275 -#, c-format -msgid "" -"Unable to load the following shaders:\n" -"%s" -msgstr "" -"Impossible de charger les shaders suivants :\n" -"%s" +#, possible-c-format +msgid "Unable to load the following shaders:\n%s" +msgstr "Impossible de charger les shaders suivants :\n%s" #: src/slic3r/GUI/Plater.cpp:3233 msgid "Unable to reload:" @@ -9708,7 +9747,7 @@ msgid "Undo" msgstr "Annuler" #: src/slic3r/GUI/GLCanvas3D.cpp:4382 -#, c-format +#, possible-c-format msgid "Undo %1$d Action" msgid_plural "Undo %1$d Actions" msgstr[0] "Annuler %1$d Action" @@ -9754,20 +9793,12 @@ msgid "UNLOCKED LOCK" msgstr "CADENAS OUVERT" #: src/slic3r/GUI/Tab.cpp:3719 -msgid "" -"UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\n" -"Click to reset all settings for current option group to the system (or default) values." -msgstr "" -"L'icône CADENAS OUVERT indique que certains paramètres ont été modifiés et ne sont pas égaux aux valeurs du système (ou par défaut) pour le groupe d'options actuel.\n" -"Cliquez pour régler tous les paramètres pour le groupe d'options actuel sur les valeurs du système (ou par défaut)." +msgid "UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\nClick to reset all settings for current option group to the system (or default) values." +msgstr "L'icône CADENAS OUVERT indique que certains paramètres ont été modifiés et ne sont pas égaux aux valeurs du système (ou par défaut) pour le groupe d'options actuel.\nCliquez pour régler tous les paramètres pour le groupe d'options actuel sur les valeurs du système (ou par défaut)." #: src/slic3r/GUI/Tab.cpp:3734 -msgid "" -"UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system (or default) value.\n" -"Click to reset current value to the system (or default) value." -msgstr "" -"L'icône CADENAS OUVERT indique que la valeur a été changée et n'est pas égale à la valeur du système (ou par défaut).\n" -"Cliquez pour réinitialiser la valeur actuelle sur les valeurs du système (ou par défaut)." +msgid "UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system (or default) value.\nClick to reset current value to the system (or default) value." +msgstr "L'icône CADENAS OUVERT indique que la valeur a été changée et n'est pas égale à la valeur du système (ou par défaut).\nCliquez pour réinitialiser la valeur actuelle sur les valeurs du système (ou par défaut)." #: src/slic3r/GUI/KBShortcutsDialog.cpp:173 msgid "Unselect gizmo or clear selection" @@ -9806,7 +9837,7 @@ msgid "up to" msgstr "jusqu'à" #: src/slic3r/GUI/GLCanvas3D.cpp:961 -#, c-format +#, possible-c-format msgid "up to %.2f mm" msgstr "jusqu'à %.2f mm" @@ -9851,10 +9882,18 @@ msgstr "Téléchargement" msgid "Upper Layer" msgstr "Couche du Haut" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:218 +msgid "Upper layer" +msgstr "Couche supérieure" + #: src/slic3r/GUI/DoubleSlider.cpp:1276 msgid "Use another extruder" msgstr "Utiliser un autre extrudeur" +#: src/slic3r/GUI/GLCanvas3D.cpp:3959 +msgid "Use CTRL+left mouse key to enter text edit mode:" +msgstr "Appuyez sur CTRL + clic gauche pour ouvrir le mode édition de texte :" + #: src/slic3r/GUI/Preferences.cpp:220 msgid "Use custom size for toolbar icons" msgstr "Utiliser une taille personnalisée pour les icônes de la barre d'outils" @@ -10026,6 +10065,50 @@ msgstr "version" msgid "Vertical shells" msgstr "Parois verticales" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:234 +msgid "Vertical slider - Add color change marker for current layer" +msgstr "Barre de défilement verticale - Ajouter un repère de changement de couleur pour la couche en cours" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:235 +msgid "Vertical slider - Delete color change marker for current layer" +msgstr "Barre de défilement verticale - Supprimer le repère de changement de couleur pour la couche en cours" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:208 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:212 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:231 +msgid "Vertical slider - Move active thumb Down" +msgstr "Barre de défilement verticale - Déplacer le curseur actif vers le Bas" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:207 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:211 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 +msgid "Vertical slider - Move active thumb Up" +msgstr "Barre de défilement verticale - Déplacer le curseur actif vers le Haut" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:231 +msgid "Vertical slider - Move current thumb Down" +msgstr "Barre de défilement verticale - Déplacer vers le Bas actuel" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 +msgid "Vertical slider - Move current thumb Up" +msgstr "Barre de défilement verticale - Déplacer vers le Haut actuel" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:233 +msgid "Vertical slider - Set lower thumb as active" +msgstr "Barre de défilement verticale - Définir le curseur du bas comme actif" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:233 +msgid "Vertical slider - Set lower thumb to current thumb" +msgstr "Barre de défilement verticale - Régler le curseur du bas sur le curseur actuel" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:232 +msgid "Vertical slider - Set upper thumb as active" +msgstr "Barre de défilement verticale - Définir le curseur du haut comme actif" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:232 +msgid "Vertical slider - Set upper thumb to current thumb" +msgstr "Barre de défilement verticale - Régler le curseur du haut sur le curseur actuel" + #: src/slic3r/GUI/GUI_Preview.cpp:265 src/slic3r/GUI/GUI_Preview.cpp:271 msgid "View" msgstr "Vue" @@ -10035,12 +10118,8 @@ msgid "View mode" msgstr "Mode de vue" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:666 -msgid "" -"Visit \"Preferences\" and check \"%1%\"\n" -"to be asked about unsaved changes again." -msgstr "" -"Accédez aux \"Préférences\" et cochez \"%1%\"\n" -"pour être à nouveau interrogé sur les modifications non enregistrées." +msgid "Visit \"Preferences\" and check \"%1%\"\nto be asked about unsaved changes again." +msgstr "Accédez aux \"Préférences\" et cochez \"%1%\"\npour être à nouveau interrogé sur les modifications non enregistrées." #: src/libslic3r/PrintConfig.cpp:3553 msgid "Visualize an already sliced and saved G-code" @@ -10105,12 +10184,12 @@ msgid "Welcome" msgstr "Bienvenue" #: src/slic3r/GUI/ConfigWizard.cpp:445 -#, c-format +#, possible-c-format msgid "Welcome to the %s Configuration Assistant" msgstr "Bienvenue dans l'Assistant de Configuration de %s" #: src/slic3r/GUI/ConfigWizard.cpp:447 -#, c-format +#, possible-c-format msgid "Welcome to the %s Configuration Wizard" msgstr "Bienvenue dans l'Assistant de Configuration de %s" @@ -10122,6 +10201,10 @@ msgstr "Que souhaitez-vous faire avec le préréglage de \"%1%\" après l'enregi msgid "When checked, the print and filament presets are shown in the preset editor even if they are marked as incompatible with the active printer" msgstr "Lorsqu'ils sont sélectionnés, les préréglages de l'imprimante et du filament sont visibles dans l'éditeur de préréglage même s'ils sont désignés comme incompatibles avec l'imprimante en cours d'utilisation" +#: src/slic3r/GUI/Preferences.cpp:122 +msgid "When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load." +msgstr "Quand cette case est cochée, lorsque vous faites glisser et déposez un projet de fichier sur l'application, une boite de dialogue apparait pour vous demander de sélectionner l'action à accomplir sur le fichier à charger." + #: src/slic3r/GUI/Preferences.cpp:156 msgid "When closing the application, always ask for unsaved changes" msgstr "Lors de la fermeture de l'application, toujours demander pour les modifications non enregistrées" @@ -10218,6 +10301,11 @@ msgstr "sera désactivé." msgid "Will inflate or deflate the sliced 2D polygons according to the sign of the correction." msgstr "Va augmenter ou diminuer les polygones 2D découpés en fonction du signe de la correction." +#: src/slic3r/GUI/GCodeViewer.cpp:2660 src/slic3r/GUI/GCodeViewer.cpp:2663 +#: src/slic3r/GUI/GUI_Preview.cpp:978 +msgid "Wipe" +msgstr "Nettoyer" + #: src/libslic3r/PrintConfig.cpp:2404 msgid "Wipe into this object" msgstr "Nettoyer dans cet objet" @@ -10284,18 +10372,8 @@ msgid "World coordinates" msgstr "Les coordonnées mondiales" #: src/slic3r/GUI/UpdateDialogs.cpp:92 -msgid "" -"Would you like to install it?\n" -"\n" -"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" -"\n" -"Updated configuration bundles:" -msgstr "" -"Voulez-vous l'installer ?\n" -"\n" -"Notez qu'un instantané complet de la configuration sera sauvegardé d'abord. Elle peut être restaurée à tout moment si vous rencontrez un problème avec la nouvelle version.\n" -"\n" -"Ensembles de configuration mis à jour :" +msgid "Would you like to install it?\n\nNote that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n\nUpdated configuration bundles:" +msgstr "Voulez-vous l'installer ?\n\nNotez qu'un instantané complet de la configuration sera sauvegardé d'abord. Elle peut être restaurée à tout moment si vous rencontrez un problème avec la nouvelle version.\n\nEnsembles de configuration mis à jour :" #: src/libslic3r/miniz_extension.cpp:151 msgid "write calledback failed" @@ -10366,7 +10444,7 @@ msgid "You can't change a type of the last solid part of the object." msgstr "Vous ne pouvez pas changer un type de la dernière partie solide de l'objet." #: src/slic3r/GUI/Plater.cpp:2352 -#, c-format +#, possible-c-format msgid "You can't to add the object(s) from %s because of one or some of them is(are) multi-part" msgstr "Vous ne pouvez pas ajouter l'objet (les objets) depuis %s car l'un d'entre eux est en plusieurs parties" @@ -10379,12 +10457,8 @@ msgid "You cannot use non-uniform scaling mode for multiple objects/parts select msgstr "Vous ne pouvez pas utiliser un mode de redimensionnement non-uniforme pour une sélection d'objets/de parties multiples" #: src/slic3r/GUI/SavePresetDialog.cpp:277 -msgid "" -"You have selected physical printer \"%1%\" \n" -"with related printer preset \"%2%\"" -msgstr "" -"Vous avez sélectionné l'imprimante physique \"%1%\"\n" -"avec le préréglage d'imprimante associé \"%2%\"" +msgid "You have selected physical printer \"%1%\" \nwith related printer preset \"%2%\"" +msgstr "Vous avez sélectionné l'imprimante physique \"%1%\"\navec le préréglage d'imprimante associé \"%2%\"" #: src/slic3r/GUI/GUI_App.cpp:1078 msgid "You have the following presets with saved options for \"Print Host upload\"" @@ -10399,7 +10473,7 @@ msgid "You must install a configuration update." msgstr "Il est nécessaire d'installer une mise à niveau de configuration." #: src/slic3r/GUI/Preferences.cpp:299 -#, c-format +#, possible-c-format msgid "You need to restart %s to make the changes effective." msgstr "Vous devez redémarrer %s afin que les modifications soient appliquées." @@ -10408,7 +10482,7 @@ msgid "You should to change a name of your printer device. It can't be saved." msgstr "Vous devez changer le nom de votre imprimante. Il ne peut pas être enregistré." #: src/slic3r/GUI/GUI_ObjectList.cpp:3884 -#, c-format +#, possible-c-format msgid "You started your selection with %s Item." msgstr "Vous avez commencé votre sélection avec l'item %s." @@ -10441,24 +10515,12 @@ msgid "Z offset" msgstr "Décalage Z" #: src/slic3r/GUI/ConfigManipulation.cpp:59 -msgid "" -"Zero first layer height is not valid.\n" -"\n" -"The first layer height will be reset to 0.01." -msgstr "" -"Une hauteur de première couche de zéro n'est pas valide.\n" -"\n" -"La hauteur de la première couche sera réinitialisée à 0,01." +msgid "Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01." +msgstr "Une hauteur de première couche de zéro n'est pas valide.\n\nLa hauteur de la première couche sera réinitialisée à 0,01." #: src/slic3r/GUI/ConfigManipulation.cpp:47 -msgid "" -"Zero layer height is not valid.\n" -"\n" -"The layer height will be reset to 0.01." -msgstr "" -"Une hauteur de couche de zéro n'est pas valide.\n" -"\n" -"La hauteur de la couche sera réinitialisée à 0,01." +msgid "Zero layer height is not valid.\n\nThe layer height will be reset to 0.01." +msgstr "Une hauteur de couche de zéro n'est pas valide.\n\nLa hauteur de la couche sera réinitialisée à 0,01." #: src/libslic3r/PrintConfig.cpp:2831 msgid "Zig-Zag" @@ -10482,12 +10544,8 @@ msgid "Zoom to Bed" msgstr "Zoomer sur le Lit" #: src/slic3r/GUI/KBShortcutsDialog.cpp:176 -msgid "" -"Zoom to selected object\n" -"or all objects in scene, if none selected" -msgstr "" -"Zoomer sur l'objet sélectionné\n" -"ou sur tous les objets sur la scène, si aucun n'est sélectionné" +msgid "Zoom to selected object\nor all objects in scene, if none selected" +msgstr "Zoomer sur l'objet sélectionné\nou sur tous les objets sur la scène, si aucun n'est sélectionné" #: src/libslic3r/PrintConfig.cpp:241 src/libslic3r/PrintConfig.cpp:816 #: src/libslic3r/PrintConfig.cpp:1748 src/libslic3r/PrintConfig.cpp:1758 diff --git a/resources/localization/it/PrusaSlicer.mo b/resources/localization/it/PrusaSlicer.mo index b8903a0f619e4a9876cd24df094e0b07bce4dfce..9c27fdbb94ed39a14cd9c88b756edf9eaeb539eb 100644 GIT binary patch delta 64942 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 Zkvb|r63hw2@92;+x?=qE{weRL{XZf1A?E-9 delta 52667 zcmZVH1#}ii+yC*+eFt|7P7)-+-GT_cE^X92pb%8oKVMcosJx& zreYk%!IhX4w_q$hiuA`hk0JOsrp18cjuR7uQTJ!Yy;v6SVc-eVfybDba?+FB=X0EJ zd_%e1X|?Y23Ys6TA$FWdT? zr~%za4d{jHdA{?-Rs^1PoOqNIVRQ_}I2eYyAup;!C9UN!A>}Hl`x>LJ?`Y5Wu?|9Y zc$h7ZK@D^gy6Vva4&*B9X4C_BqZ&Mhde9~G<3oG?DJlXVQ4JrTDdxg-SO?Q%FI2~-Ti2nI?=MsY?_y?r zhNCdqWz)`lR67Sy&%2Ad?~TiWvN^#O6OtU5h4N%fggY?_p2XOA$JRf?5X#>$1qNR= zQ&t$2eC2GpE-s9UYbKKUP}jM|IM8y7LXDsW24N@Eh=-waVHtM9O{fl~yKe5w zi|S|v)O8I}p>2!a6rkE4Zk>sVDX&G^ah-kUz`20QIq?WvpmW2_bxYLTcSPOT3zh9Z zqZ zTBo2!z5vy+jaV2DVov;u%`p4lCMU+BBD@tV<6cbeauEBD8BuyvL%A^zmch2z8#NU- zuqu8>J)r7cvkC^G8lH=a%yQI-x1c(>3p?N`)OCgbF;iF?T_r(14wPJNtvyideh@13 zqp$!@#<6%9b7O7xS#@*->i%u00qjRjr$W^&VlMk1-yW@a5{!RG)cJ!HJ}UV$D0p{zd~|{ z3ibRc>H*)Z2_BjH5Y%<~P!TJR1FlFy1rAnT2^!?HtEmcpJU@ zpWFM9vg@?vKqKjbvG6C<1AoDII0Y->LhOT&aXvQx*KzjYcTA7_UN}xB{0AFgte0jA zT4D(0ZWt3Mp*k`PvuOP<UU z^WY0>=vy0d)W{p5I^GHk;uLhVb8wV{r1%~c@;L8|DN#uoj*3J{RQA@e<$Bh3sO8(w zp8pk-P@av-m30^w|3E$GFlwr9yd(a)(D&YKxk0E7lt4uy3UxzMRH%EQMl#tt8@2A2 zphCJE)!rS{_3uy}OY*_wRxVU-MWEiAWj_%AWE?c1B02W5jzv9q397;0ZTSR-Qoe&q zI{!y=e;8_n#ZV!xgyGl^)v<+G91mh<^!;bd=5kP!iaMAWC*fjTf>W^kC&xLBS5YHb z_1Uc7Ls*LPNi2p5zL@)?P#tcD8dxt>Bu1l_^9ocuYfuq(kJy82IGT!QI0^fFH64rb zjZKMi89aj9P|MW)ZbCR3HOGrl4_Jqa#Ab}deW(ru@P8>W7*&qIbXx!Q%>ipWz-BD*B<4FJ)kWH%H+ZpK=XrGgMA=wGPMRlxLzJH=sto1vS$BsARp4YWE(hz1OG+ z21KL7Jl~1Kf%fEdmnfr(yIM0p5Nx7avpJ zg*v}HrbbK|HDw3T4dLK42WsdgY9|YfWgeUq6{_^8k>*34uY!ui54OI$tsjNDekN*} ztwOcC1vRBdQ1`vF#*Q7}dUF;W+dLpI>H(Ee7dAr8X*bMovpu#UdK^M6&S}v zGCdZjoE0@C?NROYMn!ZKhT!x#uDM`46$;To>owFIzC_&+JFc0_6sRpX6nkPZ)G}I+ zb#OlhVNg8NksPQTD1h2GN}zVW=9mS4b~(_E8&F9RExvhh0vt&>3o19(qaM5+6`^ys z{w^x1o}hB)KU7455(IePerHA9R~I#q?x?98j4VILoz8)ha4Xiu0trp%$6_tY3$X&e z!Le90ky(C+u`uO;!~x#xwg`@=JQZ_bXcF_F>Zqw~hq`_cmcy+^*ZItWdR8i_S$5S? zQ_&9FO$-or?Al5?Lz zUy1`IMOD;@dZ8|yfSqtPl2=Yp3NzOkQkp5qi|Tl1)SUOiaQp?e4A-NU-9A)~JjW{d z1@%&@n2M<HzP%-U4Y%@-#w4U?gfJlhB(A)N)&fVR!)bz$d6sf5u=8 zOlvxp5tZ$QQ5|WCWw1wD)_;8two;K1bP!U;=+WU`Vb&M7q;Jm=P zI2+4_mCKHNQ6qbX zdO+L^W(qQ(vOFs)X-lG(StV2lTA(7?4ohQCR7AJB94I8au?e2Q0hl$TX?Phbv};gF zw%gVpMcsGCdK>j__!sq{7@3TTQTL}s4J;>S!?LLR-R>M{`MIdM9FJ;X3+l$bwtU8V z-PS+Af}H<=O4^)ZrkzUG`lu;vgNp1JROpvuAv|R2UFRzYIjKk)9^ie>uZUU=lTjhv zZasm@?mMWgj+NOoT+mv}+SNJ+HK66FDLaXplIN&_Ma!ZbVEqMgpe;2kYEFxw_JNwH zY#xq^&^XlkpN&eMwdlR)Q4L>5-Twv^k=R+y{h3fxmlM_TBB;=}!z4W4>BE5<7=!A_ zVpI}svgKX2d;;}=OSb+ws-dr_NX5-&?hi$^mj|^!R6;G=8mLG#L9L>n(A6iG?Hp)? zJ5d)NMRnj32IAkgd>=KE7pM*7GwS-_?B@RLsE8FoMXHRo9x4Yqpmx@gsAQaxo%OGc zWGxk4@Hpzmh#Y2QQK%6AfC_nUR7XdnMmz)6(KT2Px1k~uFQ@598dNf8LUlB^wK(>r zTsbG}KRXA1Qqdk?phnOjm$3!v!5vW#9*jz!aj2=9hliSn8Ee12TX1;3yg%$?utR0U8wSU*%K2ctST8hha+)W`!1m}CkDg{}$yp-CYl4V?>04D_Jqsn_x5jl(6 z@$R4=_z9I%u?m|h3PyD-H>%wzRHWOYLO&Q&X#G#+Kzs2Td%+ge9G~zm;72Q{(7s1S zz=<#&ii1k3>{wpas9YF=`EeYU#r+t9(TkWV2}iZl0KN6!iUT#&7uCQBREQ^`vUCP& zN)i+`Q;`&vWT{Zsw8aUi5Wcg< zDQ+4L#az_q!hzTf)uHRCj{I$XgViZVFJZRcdZ_z;LUnL0YTww2ioop>tbd(&LWO$z z5tS^lN}7fXqC#H@S(#34)Z7n?G+8|bb$%@>H@0H~Jc{aI`cme6I4bG#pjK58tcul2 zxh6DosnC1*3KqiDr30KFu{P?4BdD$O2Kw*|X2tKg0kf82YUto~yiNH|S?;5}zg&QG zpYp}>0ZupES;0g)Tg3q9IOQoW2g?4Yl>)pU7Nx0dI#dfMQ9l7&V{nxK=O&K8wb(K$ zz_o)-$EW+G%jpvY+>-<=coefvlH|^|iV3Mp@!vH6Q>&sv^ zY>is3laO3a9covlt?@<>9HZr-8#2SXWJ_5C#>!NnXE~sQ)h}sv{pmO36 zhT&z@RDMGxdEy^bW<6x)KuJ{$^I#O}g2AYb;#brLvI>>$2T&cjgjzLpVf z^}wE}jtob2e5$QqYwP!*HoQ~ls^^zD&<6AZ_24h4w^X90W@MRBHe`BBDSt!_q?=L1`q>yo1$kQMu3L9CDKQMnbPrHNd!maKm@6ikI4kQvqEqNulC2UJJ8 zp^|J6Dv8#kLVf@>fV-%MAES1*54P-UWu_{jH4K&1#ZXgI#kD8epps-DYEFls8XAMj z;%TT3u0TET4^#*C+w$L7p7KL%i8))F>n5YxooSti*(onZJ;y!Gfkt@M`T#Y;HyDmF z+L#|IPQFF1w&8|9F6Mn98}0QqOyOdJ--*V5uHU%(PLE7enCYnMMu-YESQ#Zgta~< z<@ru;4wMXIuox~yb>xoqqcuS%Qy+%<>{i^`+&bL49QB$#ikj=^s3}X;*(7&K)PTC6 ztApVjsOPh+Ypi>%=TOOZ-x}D(lrvaMqaxJ;HNsw~DVS_sXkBmJgPPKlU0DA*cw{U5 zUCoJf_zU$BxEc4OmSgX3=4V6$F$?9T7>1`%N%ju=;}?6rPj{2d3o$SCTToN?5S24e zyR-h)z-KB#F?|m+mt|3N*cdhPey9h|z_r~$qlG|!QIAz*PuOd6qP)uQTxIh)JUB^=G_p4DrZBDq!i}ECa6_28MR7g zTbH69v<|gAH>29!YwBI+ggtQ}w*F3-x2Q>ZoMxjM^bxRF;piZnxgE zChBLdFM;Y{P1H-T5vJ7o@5RAjD#oKi8Ml9c_viEBs0a1L-8d2znKA=RhuWc%=@(Rp z$D&3$4>gd**b%p(A{9E&{KTXPM}J9^2AHK>1sd$8x?v^?}X~uNYtE8M@42es$<*i`J<>DxP}Mu8EOCU~|QIS25n&NAyj^A@_ zMa*9Uyg%nlj14()7rhS(qs;oRf;Fk1iOSw5sE+tYn+GJYhGHh_^PwWr5Y>^@SONb) zb<{V;wBsh_KO>LL$f8gqZ)VHgQOPqL6^SL*?e_d}ROD`<+Ifcxb&RoQq$N=0 zmZ+SVg$XuUdQ725>H{yab}r(MU5!hc(Z=9p*ol!wI5VKg|;;+xjLaXv>vE| zxu{5uLv?JXEw9EPt^aKtDAXrW4d1Z7K&?~X1T*5;sP&!>71H#m2J@miQWo{#78rru zFa%ejlKL2G89qQgFJL0=X#FSTKoudV4&*{5NoiCEqEHX2gGI0{Dq@RJ8_qV=yJIKn zy7Q==?mj9a|Dvvci|Tl+Nv50DoboMH zsH6XCu1|+bu5i@q$cLG+Hfn@}P}h$~W&JX2g%`06M!3_=k4zSz9{d6|1wqrz1H&*A zUT*6*pd#=)>iVOoRd5T{(NDC^!04`&6e23Z>zM!rT zSZJmw9)|PNs|={fR9VdWSE!qFAbX+a>}OO%Gf*9vZ_h79CC_%$)Eu?vFQKO92I|32 zQ6YYb8ptP%#Mny$oMu=BYvFH8SpQmP->6VeV=Xl%LviKvnNX6yH%u0M;4;4Rd3?=d4LUgnxPDYVRtpb07xolzgXT&#h!P!V~8n(J4n z2d7wWB9aC5;G(D`tbv-6rl<%ELbW>%v*H5O{l{Dm)Whqjo;^Y>r@$3vgsD&$=0UBF z(x`Vp6I6$0q8_*aH8pEd*}fM?;StnzHCLLXY=FHe_e0gY&p61%!5eFaRpy(-+SZ|{ z5wAyu@RIdk)Pth0HXTfW>UapM;ex2#DURx38B~tcL`AAO5*gR&ZBL9u<-o7zgtHL! zzzwJdj-Ybo5-JjR?D;RYKIv~J0>P+doCg)T8dwEeU^QHedd?Tjs`Vdpjmd$0s2(>& zJ*XL$!H%eXU>)ic%p+6-!E4Q&W<%`*B~T-;g_`q*)()s#>W|tNrlLBy8DnbwpW`4q zUbn_vXUYXo4Yowx*a4L*qfw!pZtIt!B6J9qWam+ldw_Zud_#pi^?K9M+^BZSqN}8- z$${2uE7acH1=Y~cm=|ZE8a#pe1auFzEFYmF@f;QE|4Zoh$ z$8TW$YaP$0A`5Osh3+;gWG_%7h_%t&7>0UaepH7dQ5|i7O1@60;--3b&)~`_CFiWc6&HWisNm>dukh)j_-Ci6hlsm2GQ6qnfx*_&9v*Dz}{FEDFA)J6Z{}<|`+cgZw zZ>Y$GZ8sgrkDB80s17zkJ+CJ+FxMHwfs$l}z2JA$+@C~U_z*Rh-%+7W^}Bg+5mW@K zp>m=lX2L$G9GZ(t(lw|^Y(#bV0A|C>7+34xzr!R$LR5CAMXmo(R72VA`C_P%RYHxZ zK5B}(qOR+Qn$uCJ0W3mwd?za8mrw(Ei<Mp73Q!XBuR&$9I!aS!E_ zsJGU*o#s!&HewJTIR4(n+YvwP=7E@MFMpbj1@`f?U|h1F*DXdrK!>s6LGx!xtq!sN zYtvwmCKt<-?qpW|8Jk~KYSD8>JvZ8jVa;Ukhg$j9VRCW(U?U)l#9omV?p)05XJVqsD zqT^;t3ZU+*hH9?`YASmjXZz zKY*DjpT`{d1+_6{J!!rHsf&t8AD095{3lcvPe3id*|-CDppvTBDbwIM)H2$N%I;&f zd>XZb-a~~v{%O-*Qq;R51QpR-sQZdxGjvOFFoJ_cI2yywm_2_3s-b^S5qXRXY2aDY zq0Ff4FNKOw70iXhuoiAY4Jg_B=ktVKQe4=jqu z@g2svVEzE%EB2yX;i7r)PAoAtUGza&~8TT}jwpieRfX`V-#cs@lO?~FVNjRJG z11yb0{XXv(3=X1RMsF|}lLz{|-*zj6g($bjHMkJ_V%2CqZ(}-yjVb?!{jpwj*XO}u+rSy4!Yi?vJpSSaU!6MYxPVIAA zpgVyBZLu#=*%>>H&l!r5sEz1PR8n5YO_-d&L*E0Bp+Z24Aa#*m>`!k|9 zn4ON52=RHVq*JJgbe!})ZqV7z+>B@<~*O&*3RtlHcd;cs&ak zN8u9c=VN{>UC^}G7rpQQhjB2G6JxO+rY>Yc-xKxn7>j!F3e?+f4QdPCjB)WV)Ruc5 z^-j2rO4jG-$Bc!2-Y2WfsCP^s)XU37R}D!>_29&V4nZmS~jB+N-iA6C1c0|3j1{7!ghjK83id47*b;AWz zh@N03{ET`@g_iI+%`pqAJO;IDreH=qg8A?{YQ&)>O(e2m80A8!0XIcGx0lO-LOTtU z<0_1g`%xV^gT?Uys^=LaP4eVNb)X~$VFlE3ZHii+eX#~UMh&D$DHFLWsN89S>WJIj zo)~E>X4nhX*z#Ufh%cZ*dleOt2Us2BmNs+Q81)^_&!~=X#1K4;+NvL+J|Dy_W2P!A zG62`9%7I2)*Hk#IQ6ue)dhj4rD2HMcj>YVF1vP?bWlfT$LhTcws8v%NwQRegIxqlr z{d`nMHe*7q|Faw@oA07V_!O1ZAMr;lQqJdn9$1g+sK2}kePU}WR5qta&2bb)U@sht zYf-BsqJqykhkY<*06#~lNC#XFT2=Bnzj5Nn%0BOJtA|wad7o&u<9X_rMftqHY}UW3 z+3B94vVTZ5Gs2%y515P!{VA-9_fR9wTiv`HDxe~=0CnAJboJS89|s!wZF|8p)JQ&{ zE=*U$yd(0XLR<}1KLp$3bW}UvQOh%3O`mfXbEB^J)iROGj|C`~L3ONuE!KZ74kl1B z75CT+YSlJ7*C^CnkHf~e1GO50>X>C#2eomH!a=wbzhmLL=DNc5eBLi4^g+E7PGb}% zsPFT>(P>=YHJ?mYQlXx{Mr|}18roxsgJC+4>cD#}hcR0cNuKXi<3J6!Mm5mR+7*>d zy{+R>*DbTIM9uYYsHxkB>hKX%#7?5xyKL)kS^u&1kI+?!UvQx0`iLrjL*~Yb(aMyQ zqUJI!Y6MwO4Hrc{s5GhrQK-4CgBp2zROtJoHl9(a=S)RiKdTk%U&*zI3N^SMb>lYc zVN|wWK{iU~9jap=tbwgfLy1rwONqKa8!AbQp|<8)m=On|BDe_Ev8AnDbHipTlzazK z54?bi#4AjXpHNAYwv8EKepF6WL}mLAsFC$Y4ah}J#duW5rl8teiW=xzR0Ou@Lfx& zkvp4%!c_c&N~Sa&%r^{qP(5FXiohSJj@?8}!7J4D-%z=cq@#&kFosethI%J7Mdip( zsP+E~p28*gT2faW4f6jp#_>Q_EdJi)<2~ZD8jv8?|YNYv5p^ZdcUmbOS z1AD$1>Oq}QbKC=!l>Jfdjj`p~=zaeGjRQ5X1=X{is2dNV9&{4bfg7k?d5n7S2YcS> zY0f7=Jtz$-H*%n^&x2YGB~epa7nN)6(EI)WAspz!S$F|gSO@j;IV~yI{Ly^Y+kzb^ z2lh5O(-qaw3e*Gkp;pBu)Bql%a_1FlN|N?5^}(nChxcLq>mVl;8c_jMh)Q57tcJlj z36(q>Ff*P&Mdlsm!@#~i@5k(gu>j?^s3e?=`s}v{v*KOUeewGFyg$H5(~tGPjEag> zWXFFo3{&?v$yOTGP(xJxKpc#Vu^@&HFy|YfMmhi$;(4fLwjQ(KF$}@Cs3}S|(7av4 zTn-evNIZu1FfWD3UMpc>v)i@pN`7@WvHF)0IHowI1<01?i=xw>Bv0m@2C!* zM{RWOeGZh>zQN|g_|`0_WmDFc8{2YURKpWd-=42TC9R)GY6|0_A`yhjg$PtTQMfvQ zmmF#;6Ablo!gaE7pb!^CHPisLd|F~{Y>!d63l*7I!_4<`#jr5tPN+FuidrR`t@~{K zNmK`Jpf<3#SQlds*JniDzfC#lO2u5viE&1l5Ea21l%r4$%tJM>6*aP>sOxXw5R5a@ zBlb%FzjbD_vKLS)I;yz|Le$s=Ao}KuoKnu{itPl5|zDgt%=5)`*NbLtAy%6T^x_?P^;w~YG6qwn2jjM1hP>ZP5~-3 zl5&^}8)I}FhwAB6)OAZy5!qtvPot*bZ&dQWKwbAAD$8R{G!M>%k(A4zo-+dV`CyLA zfgZHbo>+lOu06K?ENZ!3L2c2ma36k0t@nMCOhcDY9l4Hb?};tHLM59s+2{Q&n53v3 z@dEyZ?ne%kWP7LhoS*PAs)4#w&73zz?d2U%9qWM_=?Hs%0_p*Cuo=kN{4wzvckR8>Lf~X`dj>WJU7R0%zsX1@Ghw8{X z)Evi}X}+b)fNHlc>iQw5DVU5IwEkCeke`YZsN@NlWkMSdHP;z16PCa%*c$agV*=_y zORzHTMlHXXv(5K}C9n|XeyC*KfZ6aQY9sp(bMSm8`y3PEMySxXL@lq*7>Qf0?@`OC z&|H%f^-)>g9o5lcs1eRXMQ$CcqgzqAatyT!{*@$=Vzhp}we4&%o?>z}COSn3NMPG|N2YLf6biB`UO6 z55h{g8pH4{YOYc*@_D~5Qw{aN>9`QrqO!f(VxMyWub}q&xl7D)+=;a*AH%AcZmAh) zXVhzZq{~4F2dhyx9!KqXPf#IyiOTBu%Z%x9Hsyl27|&vH{BgO@`(yk~c!P4G6(;ma zSDFZ>Lq#?M)qYvjRJ)Zq_<@54sJ(j&D&&`K`8BGi$yS-r7Qqpe8({?A#&Vc)wa@z- z6wUAz<-f2r{{EYZ#AnolW3Mq0NQfk<>tx|TTV-xk2cl5xyc;UH)}VH_y{L`k6e@Y< zuQeOW7F78@Du+I!BAIlZ$&q}hat+j~=!RM)eZ2Fmzh5{|QY=M1a4qVA`%p=E+SXsU z<$qBd(kJ|YY1W(jl5Q|l8IGE|ins*ZqSk-xjix>v7f>#T-ul1Kfqo#EWRuDA9GlJZ zDTnGvRm_Bq(Oc)J9dn*_EvnY8Rux?(K`Rb)V-M7v zjzNv!AZoeYMXmoYsOu7KH4n&&I$r|SU<1?+*%eFTOw_Wyg1YV{s@>SzOvE#9WBn_6 z@=&4d?2Ec^7^-7)F$7nmcDm!J2;4+9^bs|}pzS6i1yJpjL+t}KkQsKmqdNW)t6{9) z%`$HAJL_LNSvM+_1A|cOek5w=TZ7t2E?^CGc9>;V6V-tqa5eTv?RXjgFcHX(>To?& zeS6fDbw}mG5L86xxg01|n@}Trjk+=3P7~5#)T$_eldu7*BaiL*BD>6=8P&kb)UUvm z_!>*$oZaR-q?=fia-uyxXBf7^TY$RYDJqgZQ5_$Miqw1@jJr_JE4tr{jO#>kpd0I;)@OUvJE0q@p{b}4&b2PZ z;*{5*vib=sw9)@GkqAQNL>E*C#-Va*J?grnsF7dAFl9%q1Li@wQ9E4(YJE3Fy$gn* zI(Q6q<1N$!;vY2E<-p;Tt78e4@p;s;P5hS$eJE-Gby4j$K@Gq~@8AF1%|QeeXHcPu zec1e1JvHX0JOcHH##?a{ z*^_1f9Zs3+(wt`f>xLqy&D=LY^}HFX$2~CwXJcmEX}yP<(|BjhRvM1`DIdnXIQp!4 zo$o>oN7#SRJcieN=WYLB03aViX?4d6?+Dxqdx{ zQr?aV@l{*@6xD$@sN{=t!6bKT)VrmmYY(cRZmf@Lr~_*5$D(fBg-WUus14~ls(~06 z&3#Ew5L@j7q8m*Uf`cp*mCm73#{Uq-|*Hdtn-$?~LLg8ZJYv%imBB+JV}N zZ=jMg*$wkfNQ-JXA8Mo(QOVl~6^R!1d>7QqY#1teS6TO=I(`{lW#=6Z6ypC-p-Ozy z+>jHsJjH&YErtUl{G7nH4eT^D;iaW0P zK#=>6*>GIc9IZfIupbq=tJasOk;k}eI*<|dplqlI6~l^H9rdo5iCT6?P*e2K`Y#Tk z{LbZ|5C`4=F-fx$$5TFFt$EMve7CU<^)c_8pY1e3ALZ{@3w;kvB9atcE$PF z8#SP$56zTiM|IpS%7H$+MWNPhOZ4L;)D--R3h{E>jq6bn=<-H?FBhN4d-=Q85^wcD8 zIBNMuTC1YoA&pRTKMYIZ1XKslV3gMXKOAUB%l*tG*$}Koc`d5JFQ^g3dTtuZfNCHY zYT1^?u~-qcOfR59e*-mue{B6nRB}51`n+G?i;v#l|NoH#H8>nA<8;)%a0k_3ix;LN z9k4g$KB#PehQa8(H1~&~_KSR|`#YmL_zZRZYismZ=19NX`GmwXxbQC<)`1}7H=t2KrF8qvXFk52p0;eMCd`nbA1F$$wMJ3-^ z)Poz-vrfA z4^-#}p*pe<6^RX~2OmXkQ1?))=Nqb>m_dGTD^G=5w&^e&b75|*kM4U8Msc8?yUG3D z7P%hFQhtRBUBMJ4M@pj_u7u_A2h_&32-T4-s9ZUX>fkF>Bx9#E<>aVH=eCwh>36-J zHKJk^Cpuwce2aQe%~WPYtx#KP7t~08M6Lgks2y$}YHR%q)$t3c2c=By_x^|`C+fN; zs0g*Pc1g{@f2yAMr9uy!VqI$8hB|-5`VZ)I1-(wZK2LyfdIX2U_K5Uxbc{Z`b251~GoT)>?86lY@P|K)1DrwuI=6X14q;pY` z+=H3$3@S(7qoycUs2NCdqw9oo(1jC)@HsBSrZ_dd>CkIb2R>s)jGMu1EO}5Nt$`Xz zBh<3&Ve2QNreGGTgNsld{{uC!Yv_Id|BeGKpEMcG`Yed*c`a){RKxR8*X_a*cnP&J zCCg;YVJ&BEf{I9Q>kR7->vi=0{@+&)@^NA2Ff(V3F+b%|sASrW3iWwgerb&!ZqBE% z=0%OLqP4wslyxO)Upayr;0<&&_rA;~2NGFBthrF3D`Bl~?QR{7YdOCdFJX-=CV9iN z`n{h6=ENM-H$Y9@&!}bi8;-|~Sy}(tIVhRUgtjvl)P<;3up2d32T?t~jk-Qsb~Ez4 zsGYGQhG1t@){n;`0elCA+H&9KF!v|SX;w=?R8qFd>6)JPqe4kE5A}d87=m|D4f=8^ zS=gdcb6OX*ocf}c-(qZnXHlytS8kKEg;5c#gnApcMBO*ky2iB!mr)(?vkI5uDNK*O3i!R>?VOJ4=nMPT_}? z4*FA31oglzsF7Sj?SwH4n;k1DDgqf%Bg%&rFcKB|QMP_Ls)L(R5xRuhy1${4F@A)Z zqO2H?=Q|ZRsEc(`BmE84z!|KFZ!iRl6)|()#M%ZmRb5aWa#152hgEPgHpH7)jgP~H ziu%1D%l%y3?=+|WFLWb02ruFHzHw-eWhpO0HS_?L8&6R=@E)}le?=|J_$AHAvY@6Q z0)NFQyo`5H%XUYkSv9w@4&{JSe(!I~)GNjM*NtULo205|?S#dtAA*%|k1fY69H`B2>p>R5Z&oD{AgbSQ}v$%6(i8YH_d-l|*l_ z9ww?}I?x_<;{a3#rlXeMGSn3Oftr$wsD0o8>cO8-?~b&U%?6eab$v4o!2zg9xN|rt z$-ySnjju5RlU4D1KT55RYIqpxhUr)i*P=rB2DKsGjxx*cA!?&aSk-hOCF-S=3w2#} z)IL)mSrx9+gaa+JE~pXqM?GkiEiXXze4Q;HMRn*3YUIyQ%j+vDGV!XJ^`0KpUKr{% zT>#aQs)qOvx;x|!Rm*5;_azZa?l(=ZmULXB)QDra_Lbv%jQ z6x1*y&W9n?N1*O+gx>G}b>%<}^hMp^qDC~$mY1PIwiA`r`%xo5g?aI|J)gLyX*dNc zg4t2~Nf}g*wMRvIC#wC!=<1`^Wezlwg0)Nok*IPV)PtI$M%E4WJ|Bvua6N|LD|u88XC52&8CM%~yIm3;kCBOZ&2)H+ni&tZ9Vnwn)-0hI$Ya5)}8b+ltM z6LJ^p1h9i*sMi0d=6>(*{iSJPvi&ux0e?#~1-VcouZ-$oM^prQp&FiydS@(0b!;ta znVv;O;udN^A5qKE*UDTUj)6Si$;p96nh(`*37qNUvmWZf@7kEDNY>V5b3s(7qfq-s z8`S+>P!H^fis%$nN0y;FxCxa@J5bl3L|5zk3I_`DW2}mwaRgRsXR`PdYMB*lZyKzK zYM?IaLCsMQ=!MGa0jTyCqB^t*b=?Wfg11r07^{Qd{QG~Q9ZbXdP$wd-4Nytc9kqOB zp|X5GYF(d2eXM?nZ83UB6Ok^c`+B3MXg=!t&8Ss%6gBXB9a;Y&9K54K4F`2Hp$$WA z5G7F~>5R(S!Kf)3hiYgwYJ`VS*WI?~Utx912|AmcX@+`Wd(>*{Z|f(y94w|{7Al0r zx|mPHWpE z`+o`tn)4ml8qcC$N(FnE8>XSQ)P<;#-$q3wK~FP+AnZsv1D3)G7>Q@>`9!_Uz|x@x zmIt+Um-Fgbe^u-SO;MrifjMykX2sp82tC5w_!<@J3_qH3RaA!tqt^FGRIaQ>MecXh zGCpGKAEA=*J$irs7q_>0DP%#NsDhe`7N~}PwB=!_5KhAoT!>1-{a6}rpr$OikNE^t z7&X#js4cuSDgrf8_q9Q{0tZ7l(3~8x-b8iaH7cv4^))vnM@1wrs-Y&Rsq10uN2B(M zEvV$$hZ^}wRQu0S$>-~5BAlil>t7dCp+XmQLe2FMR0DfaH=IB%p9fe5KiK-B{moR= zMYZEv7oc)zFKPhk2AB@#MlIj6sHti>fc38s{X&Htk9yElR4A8XI^2yK@eNc1FVXv6 zZlD=)R@4Sn5S8ssQ4i>CosA1A??tuSe2}@nzsrF_I1Mx5TGR#SZTT5irkwaE^WY|! zp7KEJd{oEwV{yEQ1u@BB6M@R8xn703?;xt94^dO>CLjWOV0zSrMXmMFTMZaO{RC7f z*P}YTA2l^st#43M5O0Vv2P#5UF%&zXrgSXk#hJ*IyUuYA0;%{L)ua2?j~GEY&QO!R zTD||14l|+6i<+|rkK#DgK2dK3AJqcLEo_1>M)H!vs=t`}Eu;L-P5u4f zG-LeE5&SgP?<~Rt{nzB65eLftHP%h2x&IxN?H5sV`3AkMce)vI5Gv#a zQOQ*ibzdh`Zj8eGxEPh(mr=R)6$@d|48QC3u-XihWW!K%xdipV-%(rW1=O6~v-SR& z<^jQ|4o09hvijBqn1k{|)cwh3nd>s4UP@(9&uKS{tkp|p5EW`*4Ju@BQ8|!#wt0Wo zM~$F4D!G2f5S)eQ@efq4OqgTZTZJ0HRn(UK4z+5Wx#ojO3RJ{Pxg6+%3aB}(h04}u zs8Dyow)i7X!t1ySJI^!UN(Id~9juA!KqJ)L_eMo*E~?#~SP_q)awpLO^L>O{g9E*E zen%~YE)5^AJRtY2+?!i9eC_k}Z}t{Y-hV@t}z@TS)PeGbM@abTJG9K|FJId)W9Fwl} zdw+kZ7%I7bL(TnZ{ESa=Gd^5p+MBc5{7~x@w$S=d@tfcKHMs$6O!6GZJlyaZi(rnm z=0`5=aRlXc*bwurGr2P!75bT|_xyb8T2v10LPhWZD%ttAzImIF;eJyf}^ zE%!rx$1@7m&}3Ub6Sa<4qNZR!YW-h8Mc@N!d8OXK#^d8dC#r)#Z8Gf)MjkBA#-K ziA4XcuK7f>YOA^60;-|+sE!3~Gs`L)DjCb8BGMG~GHHj}fCgg|oQrzUCmf1twws)p zkIISls7M{htoX0XfkKh;cN5Bts0SB9H5`R{a7R?K4a4@h8oOhX9e(fM0~&#Pc?JDp z9#|B0Ulb~0tuYh!LA5g%^-;||$iZq3&Y^du#U2Jo=(ku8@AZC0;Z-<*8#4cC zvV94*Rr!Eva3W5j{MwdB9^|Lnlpmq~MWjN9%v-MPUw-c&p_qb4IN$oP`5||XBW9Vm z#gbb8`#IRl1p!C>-tXn_#{!gF95WZrK1{x}xLz>B7XrLD`+)iR2G$^6749ag4X85`j=)CY!_s4cU^ zWxw}pwu?{=wz^_Qvti9fbLD^EKALLjQ;g&FRYP zW{)m9+SPTo^Hall8%u9J4@~^QucTm5M zbLo!X`&%+k@49}6e?QVW@{ivcgmdom5sVvBJmf7$`41dOIq0#Ok}0^0`s`0kXdgf2 z8xqRtpRp{l?Q^r0pZ?eU`cB9TmNE4=aT$JoY5v?`(QCi=k6p!a-mU8zFDm!?&zR_w-+52{ zdeo|Mmw)ySc5Uwc0LYVtk5ac)s(E1HJW1rwH`^bSny#)yGgTjccfP z!F!Cvq$$;Ne*A`dKu=UVqfxKrSgDNZQG0w*EQb}W<57`1kFI)podaF?0M(J#xD3Bw z7F?V<(0eJJK!x%SCd7vr#FTu*qm&b*4fNh>S5YsYz;x#NG^hur$8gMwia@P&{QOf7 zZbOA$COxnc&P2V8?qYiUf|)QlIMCZj%ArQu8g<=Im4j_OD_Dzw$GB(_04Xc=l`dr?z#8H4d5YDbKb-dtA>HLzx= z538Lp74}2jKNWw)-&_uwa1fcnJa9fLNw#8AJZkH2+4^_394n(aAA*WR0gR5-P!Xt$ zq1XzW;z-oH;2svmw3$pt+=d(|#62)8{)+k_u@@DId#D_V6K1kF2o=&ys0idlGMY5UPW3Q5}pIZbF|FNn+Qj#DPN97V}|GRB|jwW&aM;XSj=~>z<;P(+8}F z(KDO5Zi3AyKR|V?d=}I3dZ?ssfqAeKYT3@iqFOh{IG964^sIs2CzZwcnDRR;N_OAQ z#z-)AjzI6<`Kz2W(EBGAQ|AhFN>P6i^$!Xr&tsNhmb`)9U(;EIdT`@>ro(NqGUeW= zoY{xfdA{>E2O44S{H6otQ6rdz3f(HqjDOntr`FG?5XUKCLSF>4P_Ba|a4>4$*o`Cc zI_fptqM%v6L(qLh#bge0;DAEr!7H&8<=;_L@CkEaP~kxDrBWVCQ@({h42>{Tmk~Qr zZh+PBAS&6C6fsuE@s!76FcC{xl=ZKWL=-by?g-S4=ddS+77z6P*4nSA4JCgO^hl49rD66ZNGD*|`)v@1E zJD0z7prcR0aZu%isOwXpk}(wPVs6xG_!+ef$Dy)*HfmWfLhUPCQIS32a-h%Wk5F6c zCsZ!PDP!h56txdjMomF8)GC;Wp*Y978?|a~qvrgzHDOs(pA*%7DbxUJp>n}(WKZ-* zH8=*8`J>0qULlgssk&m zt5G}ZT2zP6p*nmOy+8lIWlub`C;qj*vGpHO5BiRpyO>qX&Xy206{%6@v!SLeKPuVE zqaN4@V`B@{{hd(}{t>;u|38)k&GACij#D4O&84rJ2}w*;PlHh*%WTW}P!TAG+LEiG zBG3-iP(Re%k3!|jQdABcL_XU(hfxD+%frs0U%lpQmP>c`^TZTfU5VJv)YI+PA!P|) zX{b*}{Q<5?Kr26RkKUB!so%%(RnGm*aS6^p<#;*%&2j;M#q2S zzc}2J%C>!x>ppXCB=_+zaeMzuS*c^N{?2icekQYlo{qE)>!;!W`#P`2xH$pW{efXP zo&TEiUveA9m>iGfzyE!0r!xn+_5%0z!vB38=3oGwh{^emUZvi@e68R@ef?=0IZFrj z@?SG<&P&||ZpvdjHIDKR)JO90wH$Az)9d)JJ9S*$*!tQBLqT>YP*@Pd4?=9Xhm{M$6IIFzWYnO?hstkAHJ6CD$LLz8DQ%u^rpX z^?ZZu$zqI$6L9#5gpOjCXTCc4PPey z0o;5^iO-YwfB$a_!gZgKz&n}hw>NMy#KwHU@k~YIgkzv;sXAX zlK0vD7aGjPxm30RJ#`M}3(S^SIlhd=W*XS+WLp{zfpHd z|NEO|sTgH@HI0k)j|k{(@hksL%=~6%f~hBpNMK^F~acLIYNX>y>MT;siw|kAlXawiQZ;hD@Gli1fo>=_krBPOuxu&;%&N^#3P9V!B1g$elW@8 zdgF`u`gwm3CJ_NpWGm#{VK+jR=#v+r8^J#^EVdr}P3$DEdJcN!=~)hHt2%g0CGDJ`19|P`Z z&8@^2>A+&Q;qb@~es(i*ljw=^H+zh|#H|4FT8!}r&3hQ+kuc&a#2vvd!>@+QLBGd} zWKnE>y)x~IH{qYsx?n2N(Ag_ag@$IJEF+W~O$YC?W0YvJ_Baq8o!$q)2txMHKJ zg-M@$bN-f~BY{X;y)4WSY#d? z(P$L4PI^T+KPM(BGFpd;Uk-nk=F4!|S1|CT4qXPp19TPj3*dU|($bd)Y-x>aF!(O@ zd*D{!?_g*-7VL}P)x9A9cb25BgXLw$rs!L8b;y<01u8S*SOJ zC$UR)!Ia?Jk`w6+PZ5T8LJz__2rD8#wYt9kkK@RF5_ttN2)_r-B7fpHr(rh)86j(k zzm5DQ;uI`#lm*J-_dql0u-w?o@Z1GEi@Zl(;9OD6Z{&F=6Qd89CGz;kLDZ0@pCOgs znO{xp#C}FgutW=*JE5&;sH@8fvD~immhb56&lT{eCmkxnZF$3~zQhr5wt#1jF6Vmu zrSORbL^fg@vB(|Bf{A&v2;)BBi^TIFJ4pRH`94b8j{Hb4kHJ*OPY1j|`Z`~*@hh>= zZsn4mJJj+KHza=pOkNiM%*jP=V<%$M`|0@`>!8^Tm`PJ(hDhlruLY_SZzX?FPriki z7Xe(df}Rj+Wpsc$%atMK6%Lo2;A1iP_}gwq4z9vL@KfabzlaaO)@)e}8-aGCxQ{_= zsW*ZABDMpKC&Ad%Tha3v%xZFFssBYi1$hx(mp3+`*T8zj7p_7KFRTZug>3>Ru^I#( z8A(GkfWskNhP5f&fH*Zkk;>ThIwXR=Ibe2DpMe#r$Eg}n4~1tLdG2%?Imx9ZcawS; zJxi#ig8u>j8F}9PhQM2r*{mTthNjsJjMrid8m7{mlVLB>eUM+Jw;%d9d45~L=#KBe z{!aY@eSzd{aA(OUBEi-h; z5^5rQwfQyqis*9UAIZ0(eh1setN#b$P%w{CkvA+MzwFV|b$TBEW1N(~B4}Koxjq_7 zoCi=efJM4k7al{ob$yoHK5bkF?z1kG0G>Y#$e+IBYLWMrSZd|S_aMm|(zxClgLf}; z$qRY@w-1u9I6-l=9D_uPGdL^x=QMt+LnOZ$U8PGsA-|bA?|Nh8^bDo8ko*bk-|#ho zZ=k0p1js?^HCd`Em~JV!{@>zkgk&%F0lEV9mRN`mlE|#f<%VDxxf&XOL#+fkkt-0d z=kou9Ux(a!^b$*+AU}i|7xd&?z|1I$^ecK^BXs+(=hNTeCTLjO0O^dleoOl@LBkSeSLKYf4k_W(75KV+wB%0V%viEAsveM*cZDkt7$MPvRqxgz5>~ksL~K z6}eZMyTuZ}(kxODzXJJitu2BqoZ1d|*{De0U~;_jZOlOBHH%T~c?CAOVSyqbkXmbi`a1G!lA26d0L z)+-`$Yx1Y)`A9B`_$Tf6U~LBPljX5*NtOavOI`pdik_pnzZcmWfHibbFC_|Lcmwho zz-5Fa75OuIsuaW?2?HbY3_a_$Y!CIf&|e>YjV8_pyar$a^aXxemPw*el3`)wuF%B$ zw;2N&vc))q(%$u$JU5f#(+)jbAla65pb?0-XEt^I=8i>*b#*Phy@C%s{`= z;u!p~3aCKS5tfm^9Fmd7%V;=$Me=9K%i%Yn)wLJbh-9&J^zUVfqbxN8tjIU`zbWrw z>@#pJ2&^!4IL~U-Vb4m1EkUG7(wfHpU^zQQrhk@j^zJcq-0sb${H>$B=R|?-!_y9N`b|3^HER$CU@`|;woBTD6_tTd|?lc;sY%$c< z(qEOFNO$~!oYcZ5pe5lfNi8#edsHNG08QfvrVu;)fG#dA;n-!FNDK=EG4L75NjLCpZ5`u~i?y8`-QU#q5yx zhu9ClyOOv%_>crP8QfbiA_pN}PFw+PNc23 z$=#Fh|FyKZgXBs4U)(+JzedYtO@J&lxrKV_65zshnG3o|b7~J+e3%ZXhc9Bm_bau< zXkRptd^Px1>&(Pm1QjTx)`m>(J|jShB%DI62wH<>t0?Y(4wia*`bCDrRSE5(@kqTQ zsmX<_KNtTz!yCH!6OB6rdENd0|5y}oPX;Y#NN;Rr2#YC!KPMjqvJSQB*wqld*Jav) zt4Dn?`4f7D?lG_p9GP_S`d}`C|Al-Sx$$4q^*rpUUXY=rzoqpmHiItYRQgCT>G2;@ zKMzedy+)&mgQ(@io<*bS%fJKI0OeQGj3!|3qqp%zegIpQIE4OntaVtv4_?)x00se= zfR@n#lFLo~SL`Yli>K)xwf%Y~Lwh?Bm(lC=hW@(bE#+82+?0h{P>+Sy}6#3E%CI|tr#y3}-P zm*C7I-v?hn(AvX{eM_M@;6G{p3g9%zOmu=S+K$uQqZSF-5_)fu?+9)ub`H4dM3IYjRQ2FWfr0N0p_CqU+D&}3q6~` z7G|M~@Z^I}Bo}dU>S^g)t5+Zf+ZN3C=zHo&qETIk7RHw5WR+5PXMzOL8GYvH%wOi(%7Qq6&Hh(q+P~vXY!fF3~fHdI8AW z(0dClq)T`(+v$BpeiD7d@rTGy*q?FM=`aDNW8gU&{~(v3n_t1MXE~7*)IE{{+)v=L zG1Mb%aazJz6#P7_N6O*&vZO~&;k0z{MX^8$JF7iMV_$%|6254T)HkwbPb0LkO@gMwI zXc5S}>dDW6`<|UlDWNrF~l8-_!anQsCkmh4}VTdXS7_CRH zB{l)jQ%=+cvfB8=l!TH?p6C_m4ZbOUe+c`43BY>fSNc417QYs=rh#8e@^^U`<@XBs zlE&FeSdZdKB{|M;2Te`Lbs#sG=GUmmr~fSUC0x77JK%XrpGXHVi}0VI-IV_m`Q^k} zO|E|#9TYzk1lhb z!&K1)V!?YP3x0e0zEhk(xJb{w5%2Ko9jpFJ>eqTya!SA?(} zOMRra6l`zejMN{HdjZ);{7mFV z?;=qN;B9mo{#bHT@#jG1EjbzbJ!Ff~*A&(>FeAE}oZu=- zFNMDmTTFY>$m5^S(?~L-l0O6($M#DhIflOs^c3mG! z^06a*e<<#nS54d&%vvK~|9#Pv=z0ZuL4)Ux13#a2q39ACf5ynMYqMdcLH$0h%7(BIFXlHc*ac_(|j|!_yr9 zi99JcNt^*V58IFAXGrSM;LnER88DFCTI?$9Sq4=FHx(^_{{bC9?U^p$ir6Dl;W$by zE8HGgNG_XVB^Q%OLmCF%(Bxp^+BAd$9LSaz86=WX>yOCIb*GF+I&3`gJ@gL4M6$!z z*K4RNNexPujKRqP?|!%v^8=oVvw`GsB@k35;@4b8k!D~-3P3soJ63VAk^{2}&8v$( z(Aoiw2f~w?<@?dQPIFP@R1%G91j9MW8*G4{vYHP3hlbfSJ_Nnf>qG?^QXcGG20y?$ zI6x`Wf^BH3s(bnq_R zTI5fI&8tLnSTJ5NNDXieS^NaKui*OIoi@@^KSf1}_rqo*cEIxueAB6~#NR}JS@K!n>Y|6cgH7}y zF#|0DP~^0pR{RhJioY35cF4bmtRB>1n%i;SgDWCAvr`U|ip z=>L*jH~IHxlmZ3R8q#a%V}^+Q!q%CI`(uj|U&pSZc^f_BAuB|#55zx%k*}ns^tE6> zCv+^eW#lgFfllcn&tzV{f~5-P3yO>jN}h9Jm?Js=rAL05`$tAc_D|?v#3&LJ9TOka zD<&a2JS3^#_1wt|t~a)lp53gUTFQZu#>u(S513X53^ia z=hbL4wf!#I+~?~YnqXG2FDICjEa#A+<|@1NF!Q;?E-}&^;b)&8XD$nHZkT2+cdndn zUbK(QFdwJ0uPru5Iqcrc%#Nv?n>U+2cE$~6(`W~jaQu36LJEPvlDX3w~9cC_r^C+1r}JNUWT%HN*)*6iiyEc)5Z?#%bu9BZHY zY<{X_*R1TAk=6dt%F)}j2e)z5^|Rl0a@6y&v-NNsw(Qt&M|gmJBg(Neojq@agI{%u zi3pF34=No}Hl%dQma#E?BYMUMHwq7K9}ycD853POC}qnQZG-E?Muf%_2Ga2 zcFpmQy$(CuM8|#qGOkaB824vFbWnV3j37dcl0k*zVxkhlBBLUT7$rkW2NjNu>>o2I zQWAF3OvewFvr&?xwY@yaQ8|_UVzHyXnKC9QPCr-dp-UYHExXl9M?Z&MeT`#gptJOD zM=@un-HrlgkhA)7E1SJ?x1)iNbHF>RfIaZ2<9>F(TG7!7QBii+TaGM#_V{~_dj58n zXO4JZ`_KzVGlx0APWQ?&C{?C5144&I#so!0hDF521PzFY4T>tXeTlWvVMkilI@8G? zJqfhC`daNx`;(tFAdNjNlQr0~t!&l-hn*vr<(tZ`U&Jc!uu}wEZPGi3RI}39OR8B@ z{CwMlM#YCZ$JDdFa8BB41v(?^S()uE^{gfq!#_5(Ow(Dov31Bcn^;3FdrEU_n7>`V zy_Gwqb88RlH)mj&b;Iu0(>fAp_a9`vci6**TAcy{xbX3@kzon;wK3KMA3M)P>vK-K z)Jp59uNi5-US}(N~h||`6%f5KQ zsv6*2c+E;{7rJKUO<}jaYmM-C`aiL~GI%=abdfUeL`a+{fM*=JO_ny(h}2O`x4V(dV}R{{Yd}yi@=H diff --git a/resources/localization/it/PrusaSlicer_it.po b/resources/localization/it/PrusaSlicer_it.po index bebc6ef5b..15c94073e 100644 --- a/resources/localization/it/PrusaSlicer_it.po +++ b/resources/localization/it/PrusaSlicer_it.po @@ -5,20 +5,11 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.4.2\n" -"Project-Id-Version: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language-Team: \n" +"X-Generator: PhraseApp (phraseapp.com)\n" #: src/slic3r/GUI/Tab.cpp:4124 -msgid "" -"\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" -"To enable \"%1%\", please switch off \"%2%\"" -msgstr "" -"\"%1%\" è disattivato perchè \"%2%\" è nella categoria \"%3%\".\n" -"Per attivare \"%1%\", si prega di spegnere \"%2%\"" +msgid "\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\nTo enable \"%1%\", please switch off \"%2%\"" +msgstr "\"%1%\" è disattivato perchè \"%2%\" è nella categoria \"%3%\".\nPer attivare \"%1%\", si prega di spegnere \"%2%\"" #: src/libslic3r/PrintConfig.cpp:249 src/libslic3r/PrintConfig.cpp:828 #: src/libslic3r/PrintConfig.cpp:1148 src/libslic3r/PrintConfig.cpp:1327 @@ -29,7 +20,7 @@ msgid "%" msgstr "%" #: src/slic3r/GUI/GLCanvas3D.cpp:969 -#, c-format +#, possible-c-format msgid "%.2f - %.2f mm" msgstr "%.2f - %.2f mm" @@ -51,140 +42,113 @@ msgid "%1%=%2% mm is too low to be printable at a layer height %3% mm" msgstr "%1%=%2% mm è troppo basso per essere un altezza layer stampabile %3% mm" #: src/slic3r/GUI/PresetHints.cpp:228 -#, c-format +#, possible-c-format msgid "%3.2f mm³/s at filament speed %3.2f mm/s." msgstr "%3.2f mm³/s alla velocità del filamento di %3.2f mm/s." #: src/slic3r/GUI/Plater.cpp:1061 -#, c-format +#, possible-c-format msgid "%d (%d shells)" msgstr "%d (%d di perimetri)" #: src/slic3r/GUI/Plater.cpp:1069 -#, c-format +#, possible-c-format msgid "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges" msgstr "%d facce degenerate, %d spigoli riparati, %d facce rimosse, %d faccee aggiunte, %d facce invertite, %d spigoli inversi" #: src/slic3r/GUI/PresetHints.cpp:269 -#, c-format +#, possible-c-format msgid "%d lines: %.2f mm" msgstr "%d linee: %.2f mm" #: src/slic3r/GUI/MainFrame.cpp:1728 -#, c-format +#, possible-c-format msgid "%d presets successfully imported." msgstr "%d preset importati correttamente." #: src/slic3r/GUI/GUI_App.cpp:718 -#, c-format -msgid "" -"%s\n" -"Do you want to continue?" -msgstr "" -"%s\n" -"Vuoi continuare?" +#, possible-c-format +msgid "%s\nDo you want to continue?" +msgstr "%s\nVuoi continuare?" #: src/slic3r/GUI/MainFrame.cpp:917 src/slic3r/GUI/MainFrame.cpp:1316 -#, c-format +#, possible-c-format msgid "%s &Website" msgstr "Sito &Web %s" #: src/slic3r/GUI/GUI_App.cpp:394 -#, c-format +#, possible-c-format msgid "%s - BREAKING CHANGE" msgstr "%s - BREAKING CHANGE" +#: src/slic3r/GUI/Plater.cpp:1410 +#, possible-c-format +msgid "%s - Drop project file" +msgstr "%s - Rilascia file progetto" + #: src/slic3r/GUI/UpdateDialogs.cpp:211 -#, c-format +#, possible-c-format msgid "%s configuration is incompatible" msgstr "configurazione %s non compatibile" #: src/slic3r/GUI/Field.cpp:223 -#, c-format +#, possible-c-format msgid "%s doesn't support percentage" msgstr "%s non supporta la percentuale" #: src/slic3r/GUI/MsgDialog.cpp:73 -#, c-format +#, possible-c-format msgid "%s error" msgstr "errore %s" #: src/slic3r/GUI/ConfigWizard.cpp:499 -#, c-format +#, possible-c-format msgid "%s Family" msgstr "Famiglia %s" #: src/slic3r/GUI/MsgDialog.cpp:74 -#, c-format +#, possible-c-format msgid "%s has encountered an error" msgstr "%s ha riscontrato un errore" #: src/slic3r/GUI/GUI_App.cpp:528 -#, c-format -msgid "" -"%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it.\n" -"\n" -"The application will now terminate." -msgstr "" -"%s ha riscontrato un errore. Probabilmente è stato causato dalla memoria piena. Se sei sicuro di avere abbastanza RAM nel sistema, questo potrebbe essere un bug e te ne saremmo grati se potessi informarci.\n" -"\n" -"L'applicazione verrà chiusa." +#, possible-c-format +msgid "%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it.\n\nThe application will now terminate." +msgstr "%s ha riscontrato un errore. Probabilmente è stato causato dalla memoria piena. Se sei sicuro di avere abbastanza RAM nel sistema, questo potrebbe essere un bug e te ne saremmo grati se potessi informarci.\n\nL'applicazione verrà chiusa." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:62 -#, c-format +#, possible-c-format msgid "%s has encountered an error. It was likely caused by running out of memory. If you are sure you have enough RAM on your system, this may also be a bug and we would be glad if you reported it." msgstr "%s ha riscontrato un errore. Probabilmente è stato causato dalla memoria piena. Se sei sicuro di avere abbastanza RAM nel sistema, questo potrebbe essere un bug e te ne saremmo grati se potessi informarci." #: src/slic3r/GUI/UpdateDialogs.cpp:309 -#, c-format +#, possible-c-format msgid "%s has no configuration updates available." msgstr "Non sono disponibili aggiornamenti di configurazione per %s." #: src/slic3r/GUI/UpdateDialogs.cpp:148 src/slic3r/GUI/UpdateDialogs.cpp:210 -#, c-format +#, possible-c-format msgid "%s incompatibility" msgstr "incompatibilità %s" #: src/slic3r/GUI/UpdateDialogs.cpp:270 -#, c-format -msgid "" -"%s now uses an updated configuration structure.\n" -"\n" -"So called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\n" -"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n" -"\n" -"Please proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." -msgstr "" -"%s adesso utilizza uno schema aggiornato di configurazioni.\n" -"\n" -"Sono stati introdotti i così detti 'Preset di sistema', che contengono i settaggi integrati predefiniti per varie stampanti. Questi preset di sistema non possono essere modificati, però l'utente può creare i propri preset ereditando le impostazioni da quelli di sistema.\n" -"Un preset ereditato può sia ereditare un valore particolare dal genitore, o sovrascriverlo con un valore personalizzato.\n" -"\n" -"Si prega di procedere con il %s che segue per impostare i nuovi preset e scegliere se abilitare gli aggiornamenti automatici del preset." +#, possible-c-format +msgid "%s now uses an updated configuration structure.\n\nSo called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\nAn inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\nPlease proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." +msgstr "%s adesso utilizza uno schema aggiornato di configurazioni.\n\nSono stati introdotti i così detti 'Preset di sistema', che contengono i settaggi integrati predefiniti per varie stampanti. Questi preset di sistema non possono essere modificati, però l'utente può creare i propri preset ereditando le impostazioni da quelli di sistema.\nUn preset ereditato può sia ereditare un valore particolare dal genitore, o sovrascriverlo con un valore personalizzato.\n\nSi prega di procedere con il %s che segue per impostare i nuovi preset e scegliere se abilitare gli aggiornamenti automatici del preset." #: src/slic3r/GUI/GUI_App.cpp:1512 -#, c-format +#, possible-c-format msgid "%s View Mode" msgstr "%s Modalità Visualizzazione" #: src/slic3r/GUI/UpdateDialogs.cpp:151 -#, c-format -msgid "" -"%s will now start updates. Otherwise it won't be able to start.\n" -"\n" -"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" -"\n" -"Updated configuration bundles:" -msgstr "" -"%s avvierà gli aggiornamenti. In caso contrario non sarà in grado di avviarsi.\n" -"\n" -"Si fa noto che prima verrà creata un'istantanea della configurazione completa. Questa potrà essere ripristinata in qualunque momento se dovesse esserci un problema con la nuova versione.\n" -"\n" -"Pacchetti di configurazione aggiornati:" +#, possible-c-format +msgid "%s will now start updates. Otherwise it won't be able to start.\n\nNote that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n\nUpdated configuration bundles:" +msgstr "%s avvierà gli aggiornamenti. In caso contrario non sarà in grado di avviarsi.\n\nSi fa noto che prima verrà creata un'istantanea della configurazione completa. Questa potrà essere ripristinata in qualunque momento se dovesse esserci un problema con la nuova versione.\n\nPacchetti di configurazione aggiornati:" #: src/slic3r/GUI/MainFrame.cpp:933 src/slic3r/GUI/MainFrame.cpp:937 #: src/slic3r/GUI/MainFrame.cpp:1329 -#, c-format +#, possible-c-format msgid "&About %s" msgstr "Inform&azioni su %s" @@ -312,6 +276,10 @@ msgstr "Fines&tra" msgid "(All)" msgstr "(Tutto)" +#: src/slic3r/GUI/Plater.cpp:1195 +msgid "(including spool)" +msgstr "(bobina inclusa)" + #: src/libslic3r/PrintConfig.cpp:1554 msgid "(minimum)" msgstr "(minimo)" @@ -328,10 +296,22 @@ msgstr "(Re)Sli&ce Ora" msgid "(Unknown)" msgstr "(Sconosciuto)" +#: src/slic3r/GUI/Plater.cpp:1195 +msgid "(weight with spool)" +msgstr "(peso con bobina)" + #: src/slic3r/GUI/MainFrame.cpp:1491 msgid ") not found." msgstr ") non trovato." +#: src/libslic3r/PrintConfig.cpp:1085 +msgid "0 (no open anchors)" +msgstr "0 (nessun ancoraggio aperto)" + +#: src/libslic3r/PrintConfig.cpp:1107 +msgid "0 (not anchored)" +msgstr "0 (non ancorato)" + #: src/libslic3r/PrintConfig.cpp:2060 msgid "0 (soluble)" msgstr "0 (solubile)" @@ -340,6 +320,10 @@ msgstr "0 (solubile)" msgid "0.2 (detachable)" msgstr "0.2 (rimovibile)" +#: src/libslic3r/PrintConfig.cpp:1090 src/libslic3r/PrintConfig.cpp:1112 +msgid "1000 (unlimited)" +msgstr "1000 (illimitato)" + #: src/slic3r/GUI/MainFrame.cpp:1234 msgid "3&D" msgstr "3&D" @@ -361,7 +345,7 @@ msgid "3Dconnexion settings" msgstr "Impostazioni 3Dconnexion" #: src/slic3r/GUI/Plater.cpp:5167 -#, c-format +#, possible-c-format msgid "3MF file exported to %s" msgstr "File 3MF esportato in %s" @@ -394,7 +378,7 @@ msgid "A toolpath outside the print area was detected." msgstr "È stato rilevato un percorso fuori dall'area di stampa." #: src/slic3r/GUI/AboutDialog.cpp:212 src/slic3r/GUI/AboutDialog.cpp:215 -#, c-format +#, possible-c-format msgid "About %s" msgstr "Informazioni su %s" @@ -403,7 +387,7 @@ msgid "above" msgstr "sopra" #: src/slic3r/GUI/GLCanvas3D.cpp:965 -#, c-format +#, possible-c-format msgid "above %.2f mm" msgstr "sopra %.2f mm" @@ -424,6 +408,10 @@ msgstr "Precisione" msgid "Accurate" msgstr "Precisa" +#: src/slic3r/GUI/Plater.cpp:1423 +msgid "Action" +msgstr "Azione" + #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:78 msgid "Activate" msgstr "Attiva" @@ -540,7 +528,7 @@ msgid "Add modifier" msgstr "Aggiungi modificatore" #: src/libslic3r/PrintConfig.cpp:515 -#, c-format +#, possible-c-format msgid "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r keeps adding perimeters, until more than 70% of the loop immediately above is supported." msgstr "Aggiunge più perimetri quando necessario per evitare spazi tra i perimetri inclinati. Slic3r continua ad aggiungere perimetri fino a quando almeno il 70% del giro immediatamente sopra sarà supportato." @@ -711,6 +699,10 @@ msgstr "Allinea XY" msgid "Aligned" msgstr "Allineato" +#: src/libslic3r/PrintConfig.cpp:470 src/libslic3r/PrintConfig.cpp:902 +msgid "Aligned Rectilinear" +msgstr "Allineato Rettilineo " + #: src/slic3r/GUI/ConfigWizard.cpp:308 src/slic3r/GUI/ConfigWizard.cpp:598 #: src/slic3r/GUI/Tab.cpp:3507 src/slic3r/GUI/UnsavedChangesDialog.cpp:921 msgid "All" @@ -736,10 +728,18 @@ msgstr "Saranno rimossi tutti gli oggetti, continuare?" msgid "All settings changes will be discarded." msgstr "Tutte le modifiche alle impostazioni verranno eliminate." +#: src/libslic3r/PrintConfig.cpp:1212 +msgid "All solid surfaces" +msgstr "Tutte le superfici solide" + #: src/slic3r/GUI/ConfigWizard.cpp:307 msgid "All standard" msgstr "Tutto standard" +#: src/libslic3r/PrintConfig.cpp:1210 +msgid "All top surfaces" +msgstr "Tutte le superfici superiori" + #: src/libslic3r/miniz_extension.cpp:121 msgid "allocation failed" msgstr "allocazione fallita" @@ -770,17 +770,13 @@ msgid "Always ask for unsaved changes when selecting new preset" msgstr "Chiedere sempre riguardo le modifiche non salvate quando si seleziona un nuovo preset" #: src/slic3r/GUI/Plater.cpp:5135 -#, c-format +#, possible-c-format msgid "AMF file exported to %s" msgstr "File AMF esportato in %s" #: src/slic3r/GUI/GLCanvas3D.cpp:638 -msgid "" -"An object outside the print area was detected.\n" -"Resolve the current problem to continue slicing." -msgstr "" -"È stato rilevato un oggetto al di fuori dell'area di stampa.\n" -"Risolvere il problema per continuare lo slicing." +msgid "An object outside the print area was detected.\nResolve the current problem to continue slicing." +msgstr "È stato rilevato un oggetto al di fuori dell'area di stampa.\nRisolvere il problema per continuare lo slicing." #: src/slic3r/GUI/GLCanvas3D.cpp:633 msgid "An object outside the print area was detected." @@ -799,6 +795,10 @@ msgstr "Qualunque freccia" msgid "Any modifications should be saved as a new preset inherited from this one." msgstr "Qualunque modifica deve essere salvata come un nuovo preset ereditato da questo." +#: src/libslic3r/PrintConfig.cpp:162 +msgid "API key" +msgstr "Chiave API" + #: src/libslic3r/PrintConfig.cpp:106 msgid "API Key / Password" msgstr "Chiave API / Password" @@ -829,12 +829,8 @@ msgid "Are you sure you want to %1% the selected preset?" msgstr "Sei sicuro di voler %1% il preset selezionato?" #: src/slic3r/GUI/FirmwareDialog.cpp:902 -msgid "" -"Are you sure you want to cancel firmware flashing?\n" -"This could leave your printer in an unusable state!" -msgstr "" -"Sei sicuro di voler annullare il flash del firmware?\n" -"Questo potrebbe lasciare la tua stampante in una condizione inutilizzabile!" +msgid "Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!" +msgstr "Sei sicuro di voler annullare il flash del firmware?\nQuesto potrebbe lasciare la tua stampante in una condizione inutilizzabile!" #: src/slic3r/GUI/DoubleSlider.cpp:2122 src/slic3r/GUI/DoubleSlider.cpp:2142 msgid "Are you sure you want to continue?" @@ -865,6 +861,10 @@ msgstr "Intorno all'oggetto" msgid "Arrange" msgstr "Disponi" +#: src/slic3r/GUI/GLCanvas3D.cpp:3889 +msgid "Arrange options" +msgstr "Opzioni di disposizione" + #: src/slic3r/GUI/GLCanvas3D.cpp:4859 src/slic3r/GUI/KBShortcutsDialog.cpp:152 msgid "Arrange selection" msgstr "Disponi selezione" @@ -927,6 +927,18 @@ msgstr "Chiedere riguardo le modifiche non salvate alla chiusura del programma" msgid "Ask for unsaved changes when selecting new preset" msgstr "Chiedere riguardo le modifiche non salvate quando si seleziona un nuovo preset" +#: src/slic3r/GUI/ConfigWizard.cpp:1183 src/slic3r/GUI/Preferences.cpp:91 +msgid "Associate .3mf files to PrusaSlicer" +msgstr "Associa i file .3mf a PrusaSlicer" + +#: src/slic3r/GUI/Preferences.cpp:177 +msgid "Associate .gcode files to PrusaSlicer G-code Viewer" +msgstr "Associa i file .gcode al Visualizzatore G-code di PrusaSlicer" + +#: src/slic3r/GUI/ConfigWizard.cpp:1184 src/slic3r/GUI/Preferences.cpp:98 +msgid "Associate .stl files to PrusaSlicer" +msgstr "Associa i file .stl a PrusaSlicer" + #: src/slic3r/GUI/GUI_App.cpp:1878 src/slic3r/GUI/Jobs/SLAImportJob.cpp:210 #: src/slic3r/GUI/Plater.cpp:2256 src/slic3r/GUI/Tab.cpp:3189 msgid "Attention!" @@ -950,12 +962,12 @@ msgid "Auto-generate points" msgstr "Genera punti automaticamente" #: src/slic3r/GUI/Plater.cpp:1066 -#, c-format +#, possible-c-format msgid "Auto-repaired (%d errors)" msgstr "Auto-riparati (%d errori)" #: src/slic3r/GUI/GUI_ObjectList.cpp:386 -#, c-format +#, possible-c-format msgid "Auto-repaired (%d errors):" msgstr "Auto-riparati (%d errori):" @@ -1004,20 +1016,12 @@ msgid "BACK ARROW" msgstr "FRECCIA INDIETRO" #: src/slic3r/GUI/Tab.cpp:3727 -msgid "" -"BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" -"Click to reset all settings for the current option group to the last saved preset." -msgstr "" -"L'icona FRECCIA INDIETRO indica che le impostazioni sono state cambiate e non corrispondono all'ultimo preset salvato per il seguente gruppo di opzioni.\n" -"Clicca per reimpostare all'ultimo preset salvato tutte le impostazioni per il seguente gruppo di opzioni." +msgid "BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\nClick to reset all settings for the current option group to the last saved preset." +msgstr "L'icona FRECCIA INDIETRO indica che le impostazioni sono state cambiate e non corrispondono all'ultimo preset salvato per il seguente gruppo di opzioni.\nClicca per reimpostare all'ultimo preset salvato tutte le impostazioni per il seguente gruppo di opzioni." #: src/slic3r/GUI/Tab.cpp:3741 -msgid "" -"BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" -"Click to reset current value to the last saved preset." -msgstr "" -"L'icona FRECCIA ALL'INDIETRO indica che il valore è stato cambiato e non corrisponde all'ultimo preset salvato.\n" -"Cliccare per reimpostare il valore corrente all'ultimo preset salvato." +msgid "BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\nClick to reset current value to the last saved preset." +msgstr "L'icona FRECCIA ALL'INDIETRO indica che il valore è stato cambiato e non corrisponde all'ultimo preset salvato.\nCliccare per reimpostare il valore corrente all'ultimo preset salvato." #: src/slic3r/GUI/Preferences.cpp:72 msgid "Background processing" @@ -1217,24 +1221,16 @@ msgid "buffer too small" msgstr "buffer troppo piccolo" #: src/slic3r/GUI/GUI_App.cpp:1152 -msgid "" -"But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" -"Settings will be available in physical printers settings." -msgstr "" -"Ma da questa versione di PrusaSlicer non mostriamo più queste informazioni nelle impostazioni della stampante.\n" -"Le impostazioni saranno disponibili nelle impostazioni delle stampanti fisiche." +msgid "But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\nSettings will be available in physical printers settings." +msgstr "Ma da questa versione di PrusaSlicer non mostriamo più queste informazioni nelle impostazioni della stampante.\nLe impostazioni saranno disponibili nelle impostazioni delle stampanti fisiche." #: src/slic3r/GUI/ButtonsDescription.cpp:16 msgid "Buttons And Text Colors Description" msgstr "Descrizione colori testo e pulsanti" #: src/slic3r/GUI/GUI_App.cpp:1084 -msgid "" -"By default new Printer devices will be named as \"Printer N\" during its creation.\n" -"Note: This name can be changed later from the physical printers settings" -msgstr "" -"Per impostazione predefinita le nuove stampanti saranno denominate \"Printer N\" durante la loro creazione.\n" -"Nota: Questo nome può essere cambiato in seguito dalle impostazioni delle stampanti fisiche" +msgid "By default new Printer devices will be named as \"Printer N\" during its creation.\nNote: This name can be changed later from the physical printers settings" +msgstr "Per impostazione predefinita le nuove stampanti saranno denominate \"Printer N\" durante la loro creazione.\nNota: Questo nome può essere cambiato in seguito dalle impostazioni delle stampanti fisiche" #: src/slic3r/GUI/PresetHints.cpp:222 msgid "by the print profile maximum" @@ -1276,32 +1272,16 @@ msgid "Cannot calculate extrusion width for %1%: Variable \"%2%\" not accessible msgstr "Non è possibile calcolare la larghezza di estrusione per %1%: Variabile \"%2%\" non accessibile." #: src/slic3r/GUI/GUI_ObjectList.cpp:3400 -msgid "" -"Cannot insert a new layer range after the current layer range.\n" -"Current layer range overlaps with the next layer range." -msgstr "" -"Non è possibile inserire un nuovo intervallo layer dopo quello attuale.\n" -"L'intervallo layer attuale si sovrappone alla quello successivo." +msgid "Cannot insert a new layer range after the current layer range.\nCurrent layer range overlaps with the next layer range." +msgstr "Non è possibile inserire un nuovo intervallo layer dopo quello attuale.\nL'intervallo layer attuale si sovrappone alla quello successivo." #: src/slic3r/GUI/GUI_ObjectList.cpp:3391 -msgid "" -"Cannot insert a new layer range after the current layer range.\n" -"The next layer range is too thin to be split to two\n" -"without violating the minimum layer height." -msgstr "" -"Non è possibile inserire un nuovo intervallo layer dopo quello attuale.\n" -"L'intervallo layer successivo è troppo sottile per essere diviso in due\n" -"senza violare l'altezza layer minima." +msgid "Cannot insert a new layer range after the current layer range.\nThe next layer range is too thin to be split to two\nwithout violating the minimum layer height." +msgstr "Non è possibile inserire un nuovo intervallo layer dopo quello attuale.\nL'intervallo layer successivo è troppo sottile per essere diviso in due\nsenza violare l'altezza layer minima." #: src/slic3r/GUI/GUI_ObjectList.cpp:3395 -msgid "" -"Cannot insert a new layer range between the current and the next layer range.\n" -"The gap between the current layer range and the next layer range\n" -"is thinner than the minimum layer height allowed." -msgstr "" -"Non è possibile inserire un nuovo intervallo layer tra quello attuale e quello successivo.\n" -"Lo spazio tra l'intervallo layer corrente e quello successivo\n" -"è più sottile dell'altezza layer minima consentita." +msgid "Cannot insert a new layer range between the current and the next layer range.\nThe gap between the current layer range and the next layer range\nis thinner than the minimum layer height allowed." +msgstr "Non è possibile inserire un nuovo intervallo layer tra quello attuale e quello successivo.\nLo spazio tra l'intervallo layer corrente e quello successivo\nè più sottile dell'altezza layer minima consentita." #: src/slic3r/GUI/SavePresetDialog.cpp:137 msgid "Cannot overwrite a system profile." @@ -1368,7 +1348,7 @@ msgid "Change Extruders" msgstr "Cambio Estrusori" #: src/slic3r/GUI/GUI_ObjectSettings.cpp:157 -#, c-format +#, possible-c-format msgid "Change Option %s" msgstr "Modifica Opzione %s" @@ -1453,10 +1433,18 @@ msgstr "Cerchio" msgid "Circular" msgstr "Circolare" +#: src/slic3r/GUI/GLCanvas3D.cpp:3961 +msgid "Clearance size" +msgstr "Dimensioni di spazio libero" + #: src/slic3r/GUI/GLCanvas3D.cpp:5028 src/slic3r/GUI/GLCanvas3D.cpp:5067 msgid "Click right mouse button to open/close History" msgstr "Cliccare con il tasto destro del mouse per aprire/chiudere la Cronologia" +#: src/slic3r/GUI/GLCanvas3D.cpp:4341 +msgid "Click right mouse button to show arrangement options" +msgstr "Cliccare con il tasto destro del mouse per visualizzare le opzioni di disposizione" + #: src/slic3r/GUI/GUI_ObjectList.cpp:451 msgid "Click the icon to change the object printable property" msgstr "Clicca l'icona per cambiare le proprietà di stampa dell'oggetto" @@ -1524,7 +1512,7 @@ msgid "Color change (\"%1%\") for Extruder %2%" msgstr "Cambio colore (\"%1%\") per Estrusore %2%" #: src/slic3r/GUI/GLCanvas3D.cpp:1001 -#, c-format +#, possible-c-format msgid "Color change for Extruder %d at %.2f mm" msgstr "Cambio colore per Estrusore %d a %.2f mm" @@ -1641,6 +1629,14 @@ msgstr "Configurazione Guidata" msgid "Confirmation" msgstr "Conferma" +#: src/libslic3r/PrintConfig.cpp:1070 +msgid "Connect an infill line to an internal perimeter with a short segment of an additional perimeter. If expressed as percentage (example: 15%) it is calculated over infill extrusion width. PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment shorter than infill_anchor_max is found, the infill line is connected to a perimeter segment at just one side and the length of the perimeter segment taken is limited to this parameter, but no longer than anchor_length_max. Set this parameter to zero to disable anchoring perimeters connected to a single infill line." +msgstr "Collega una linea di riempimento ad un perimetro interno con un breve segmento di un perimetro aggiuntivo. Se espresso in percentuale (esempio: 15%) viene calcolato sulla larghezza di estrusione di riempimento. PrusaSlicer cerca di collegare due linee di riempimento vicine ad un breve segmento di perimetro. Se non viene rilevato alcun segmento perimetrale più corto di infill_anchor_max, la linea di riempimento viene collegata ad un segmento perimetrale su un solo lato e la lunghezza del segmento perimetrale considerato è limitata a questo parametro, ma non più lunga di anchor_length_max. Impostare questo parametro a zero per disabilitare i perimetri di ancoraggio collegati ad una singola linea di riempimento." + +#: src/libslic3r/PrintConfig.cpp:1097 +msgid "Connect an infill line to an internal perimeter with a short segment of an additional perimeter. If expressed as percentage (example: 15%) it is calculated over infill extrusion width. PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment shorter than this parameter is found, the infill line is connected to a perimeter segment at just one side and the length of the perimeter segment taken is limited to infill_anchor, but no longer than this parameter. Set this parameter to zero to disable anchoring." +msgstr "Collegare una linea di riempimento ad un perimetro interno con un breve segmento di un perimetro aggiuntivo. Se espresso in percentuale (esempio: 15%) viene calcolato sulla larghezza di estrusione del riempimento. PrusaSlicer prova a collegare due linee di riempimento vicine ad un segmento di perimetro corto. Se non viene trovato un segmento perimetrale più corto di questo parametro, la linea di riempimento viene collegata ad un segmento perimetrale su un solo lato e la lunghezza del segmento perimetrale considerato è limitata a infill_anchor, ma non più lunga di questo parametro. Impostare questo parametro a zero per disabilitare l'ancoraggio." + #: src/slic3r/GUI/Tab.cpp:4046 msgid "Connection of the support sticks and junctions" msgstr "Connessione delle barre di supporto e giunzioni" @@ -1746,12 +1742,8 @@ msgid "Copying of the temporary G-code to the output G-code failed" msgstr "Copia del G-code temporaneo nel G-code di output non riuscita" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:163 -msgid "" -"Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\n" -"Error message: %1%" -msgstr "" -"Copia del G-code temporaneo sul G-code di uscita non riuscita. Forse la scheda SD è bloccata in scrittura?\n" -"Messaggio di errore: %1%" +msgid "Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%" +msgstr "Copia del G-code temporaneo sul G-code di uscita non riuscita. Forse la scheda SD è bloccata in scrittura?\nMessaggio di errore: %1%" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:147 msgid "Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp." @@ -1852,7 +1844,7 @@ msgid "CURL init has failed. PrusaSlicer will be unable to establish network con msgstr "CURL init non riuscito. PrusaSlicer non sarà in grado di stabilire connessioni di rete. Vedere i log per ulteriori dettagli." #: src/slic3r/GUI/wxExtensions.cpp:624 -#, c-format +#, possible-c-format msgid "Current mode is %s" msgstr "La modalità corrente è %s" @@ -2088,7 +2080,7 @@ msgid "Delete Object" msgstr "Elimina Oggetto" #: src/slic3r/GUI/GUI_ObjectSettings.cpp:104 -#, c-format +#, possible-c-format msgid "Delete Option %s" msgstr "Elimina Opzione %s" @@ -2266,7 +2258,7 @@ msgstr "Disabilita la retrazione quando la traiettoria del movimento non oltrepa #: src/slic3r/GUI/UnsavedChangesDialog.cpp:641 msgid "Discard" -msgstr "Annulla" +msgstr "Elimina" #: src/slic3r/GUI/DoubleSlider.cpp:1066 msgid "Discard all custom changes" @@ -2350,15 +2342,9 @@ msgid "Do not rearrange the given models before merging and keep their original msgstr "Non disporre i modelli prima dell’unione e mantieni le coordinate XY originali." #: src/slic3r/GUI/Field.cpp:288 -#, c-format -msgid "" -"Do you mean %s%% instead of %s %s?\n" -"Select YES if you want to change this value to %s%%, \n" -"or NO if you are sure that %s %s is a correct value." -msgstr "" -"Intendevi %s invece di %s %s?\n" -"Seleziona SI se vuoi cambiare il valore a %s %%,\n" -"o NO se sei sicuro che %s %s è il valore corretto." +#, possible-c-format +msgid "Do you mean %s%% instead of %s %s?\nSelect YES if you want to change this value to %s%%, \nor NO if you are sure that %s %s is a correct value." +msgstr "Intendevi %s invece di %s %s?\nSeleziona SI se vuoi cambiare il valore a %s %%,\no NO se sei sicuro che %s %s è il valore corretto." #: src/slic3r/GUI/DoubleSlider.cpp:2138 msgid "Do you want to delete all saved tool changes?" @@ -2396,6 +2382,10 @@ msgstr "Non disporre" msgid "Don't notify about new releases any more" msgstr "Non notificare più i nuovi rilasci" +#: src/slic3r/GUI/Plater.cpp:1431 +msgid "Don't show again" +msgstr "Non mostrare più" + #: src/libslic3r/PrintConfig.cpp:403 msgid "Don't support bridges" msgstr "Non supportare i bridge" @@ -2516,7 +2506,7 @@ msgid "Eject SD card / Flash drive after the G-code was exported to it." msgstr "Espelli scheda SD / Memoria flash dopo l'esportazione del G-code in essa." #: src/slic3r/GUI/Plater.cpp:2034 -#, c-format +#, possible-c-format msgid "Ejecting of device %s(%s) has failed." msgstr "Espulsione del dispositivo %s(%s) non riuscita." @@ -2573,6 +2563,14 @@ msgstr "Attiva stiratura" msgid "Enable ironing of the top layers with the hot print head for smooth surface" msgstr "Abilitare la stiratura degli strati superiori con la testina di stampa a caldo per una superficie liscia" +#: src/slic3r/GUI/GLCanvas3D.cpp:3901 +msgid "Enable rotations (slow)" +msgstr "Attiva rotazioni (lento)" + +#: src/slic3r/GUI/Preferences.cpp:207 +msgid "Enable support for legacy 3DConnexion devices" +msgstr "Abilitare il supporto per i dispositivi 3DConnexion" + #: src/libslic3r/PrintConfig.cpp:2009 msgid "Enable support material generation." msgstr "Abilita la generazione di materiale di supporto." @@ -2673,6 +2671,10 @@ msgstr "Inserisci il numero di copie:" msgid "Enter the temperature needed for extruding your filament." msgstr "Inserisci la temperatura necessaria per estrudere il filamento." +#: src/libslic3r/PrintConfig.cpp:813 +msgid "Enter weight of the empty filament spool. One may weigh a partially consumed filament spool before printing and one may compare the measured weight with the calculated weight of the filament with the spool to find out whether the amount of filament on the spool is sufficient to finish the print." +msgstr "Inserire il peso della bobina del filamento vuota. È possibile pesare una bobina di filamento parzialmente consumata prima della stampa e confrontare il peso misurato con il peso calcolato del filamento con la bobina per scoprire se la quantità di filamento sulla bobina è sufficiente a terminare la stampa." + #: src/libslic3r/PrintConfig.cpp:797 msgid "Enter your filament cost per kg here. This is only for statistical information." msgstr "Inserisci qui il costo del filamento per kg. È solo un'informazione statistica." @@ -2691,7 +2693,7 @@ msgstr "Inserimento supporti Paint-on" #: src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp:40 msgid "Entering Seam painting" -msgstr "Inserimento pittura giunzione" +msgstr "Inserimento Pittura giunzione" #: src/slic3r/GUI/MainFrame.cpp:1003 src/slic3r/GUI/MainFrame.cpp:1486 #: src/slic3r/GUI/PrintHostDialogs.cpp:231 @@ -2699,7 +2701,7 @@ msgid "Error" msgstr "Errore" #: src/slic3r/GUI/FirmwareDialog.cpp:645 -#, c-format +#, possible-c-format msgid "Error accessing port at %s: %s" msgstr "Errore nell'accedere alla porta a%s: %s" @@ -2708,12 +2710,12 @@ msgid "Error during reload" msgstr "Errore durante il ri-caricamento" #: src/slic3r/GUI/Plater.cpp:5172 -#, c-format +#, possible-c-format msgid "Error exporting 3MF file %s" msgstr "Errore nell'esportazione del file 3MF %s" #: src/slic3r/GUI/Plater.cpp:5138 -#, c-format +#, possible-c-format msgid "Error exporting AMF file %s" msgstr "Errore nell'esportazione del file AMF %s" @@ -2760,7 +2762,7 @@ msgid "ERROR:" msgstr "ERRORE:" #: src/slic3r/GUI/FirmwareDialog.cpp:647 -#, c-format +#, possible-c-format msgid "Error: %s" msgstr "Errore: %s" @@ -2795,7 +2797,7 @@ msgid "Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm" msgstr "%1% %2% mm eccessivi per essere stampabili con un diametro ugello di %3% mm" #: src/slic3r/GUI/UpdateDialogs.cpp:191 src/slic3r/GUI/UpdateDialogs.cpp:246 -#, c-format +#, possible-c-format msgid "Exit %s" msgstr "Chiudi %s" @@ -3053,7 +3055,7 @@ msgstr "Estrusore" #: src/slic3r/GUI/DoubleSlider.cpp:1263 src/slic3r/GUI/DoubleSlider.cpp:1297 #: src/slic3r/GUI/GLCanvas3D.cpp:983 src/slic3r/GUI/GUI_ObjectList.cpp:1832 #: src/slic3r/GUI/Tab.cpp:2489 src/libslic3r/GCode/PreviewData.cpp:450 -#, c-format +#, possible-c-format msgid "Extruder %d" msgstr "Estrusore %d" @@ -3065,9 +3067,13 @@ msgstr "Estrusore (attrezzo) viene cambiato a Estrusore \"%1%\"" msgid "Extruder changed to" msgstr "Cambia estrusore a" +#: src/slic3r/GUI/Tab.cpp:1589 +msgid "Extruder clearance" +msgstr "Margine estrusore" + #: src/slic3r/GUI/Tab.cpp:1563 msgid "Extruder clearance (mm)" -msgstr "Spazio libero per l'estrusore (mm)" +msgstr "Margine estrusore (mm)" #: src/libslic3r/PrintConfig.cpp:558 msgid "Extruder Color" @@ -3209,6 +3215,10 @@ msgstr "filamento" msgid "Filament and Nozzle Diameters" msgstr "Diametro filamento e ugello" +#: src/slic3r/GUI/Plater.cpp:1189 +msgid "Filament at extruder %1%" +msgstr "Filamento all'estrusore %1%" + #: src/slic3r/GUI/ConfigWizard.cpp:1349 msgid "Filament Diameter:" msgstr "Diametro del filamento:" @@ -3313,10 +3323,22 @@ msgstr "scrittura file fallita" msgid "Filename" msgstr "Nome file" +#: src/slic3r/GUI/ConfigWizard.cpp:1181 +msgid "Files association" +msgstr "Associazione file" + #: src/libslic3r/PrintConfig.cpp:811 msgid "Fill angle" msgstr "Angolo riempimento" +#: src/slic3r/GUI/Plater.cpp:1651 +msgid "Fill bed" +msgstr "Riempi piano" + +#: src/slic3r/GUI/Plater.cpp:3936 +msgid "Fill bed with instances" +msgstr "Riempi piano con istanze" + #: src/libslic3r/PrintConfig.cpp:825 msgid "Fill density" msgstr "Densità riempimento" @@ -3337,6 +3359,10 @@ msgstr "Trama riempimento generale a bassa densità." msgid "Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells." msgstr "Trama per riempimento superiore. Questo influenza solamente il layer superiore esterno visibile, e non i gusci solidi adiacenti." +#: src/slic3r/GUI/Plater.cpp:3936 +msgid "Fill the remaining area of bed with instances of the selected object" +msgstr "Riempire l'area rimanente del piano con le istanze dell'oggetto selezionato" + #: src/slic3r/GUI/BonjourDialog.cpp:225 msgid "Finished" msgstr "Finito" @@ -3452,17 +3478,11 @@ msgstr "Solo per rinforzi supporto" #. TRN Description for "WHITE BULLET" #: src/slic3r/GUI/Tab.cpp:3702 -msgid "" -"for the left button: indicates a non-system (or non-default) preset,\n" -"for the right button: indicates that the settings hasn't been modified." -msgstr "" -"per il tasto sinistro: indica un preset non di sistema (o non-predefinito),\n" -"per il tasto destro: indica che le impostazioni non sono state modificate." +msgid "for the left button: indicates a non-system (or non-default) preset,\nfor the right button: indicates that the settings hasn't been modified." +msgstr "per il tasto sinistro: indica un preset non di sistema (o non-predefinito),\nper il tasto destro: indica che le impostazioni non sono state modificate." #: src/slic3r/GUI/ConfigManipulation.cpp:135 -msgid "" -"For the Wipe Tower to work with the soluble supports, the support layers\n" -"need to be synchronized with the object layers." +msgid "For the Wipe Tower to work with the soluble supports, the support layers\nneed to be synchronized with the object layers." msgstr "Per far sì che la torre di spurgo funzioni con i supporti solubili, i layer dei supporti devono essere sincronizzati con quelli del modello." #: src/libslic3r/Print.cpp:1422 @@ -3505,17 +3525,17 @@ msgstr "Vista anteriore" msgid "full profile name" msgstr "nome completo profilo" +#: src/libslic3r/PrintConfig.cpp:817 +msgid "g" +msgstr "g" + #: src/slic3r/GUI/MainFrame.cpp:1527 msgid "G-code" msgstr "G-code" #: src/slic3r/GUI/DoubleSlider.cpp:1146 -msgid "" -"G-code associated to this tick mark is in a conflict with print mode.\n" -"Editing it will cause changes of Slider data." -msgstr "" -"Il G-code associato a questo segno di spunta è in conflitto con la modalità di stampa.\n" -"La modifica causerà cambiamenti nei dati della barra di scorrimento." +msgid "G-code associated to this tick mark is in a conflict with print mode.\nEditing it will cause changes of Slider data." +msgstr "Il G-code associato a questo segno di spunta è in conflitto con la modalità di stampa.\nLa modifica causerà cambiamenti nei dati della barra di scorrimento." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:165 msgid "G-code file exported to %1%" @@ -3771,7 +3791,7 @@ msgid "Heights at which a filament change is to occur." msgstr "Altezze alle quali i cambi di filamento devono avvenire." #: src/slic3r/GUI/ConfigWizard.cpp:451 -#, c-format +#, possible-c-format msgid "Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print." msgstr "Ciao, benvenuto su %s! La %s ti aiuterà con la configurazione iniziale; giusto qualche impostazione e sarai pronto a stampare." @@ -3791,6 +3811,10 @@ msgstr "Aiuto (opzioni SLA)" msgid "Here you can adjust required purging volume (mm³) for any given pair of tools." msgstr "Qui è possibile regolare il volume di spurgo necessario (mm³) per ogni coppia di attrezzi." +#: src/slic3r/GUI/DoubleSlider.cpp:1849 +msgid "Hide ruler" +msgstr "Nascondi righello" + #: src/libslic3r/PrintConfig.cpp:1017 msgid "High extruder current on filament swap" msgstr "Alta corrente estrusore al cambio filamento" @@ -3856,6 +3880,42 @@ msgstr "Nido d'ape" msgid "Horizontal shells" msgstr "Gusci orizzontali" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:209 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:213 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:238 +msgid "Horizontal slider - Move active thumb Left" +msgstr "Cursore di scorrimento orizzontale - Sposta a sinistra il cursore attivo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:210 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:214 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:239 +msgid "Horizontal slider - Move active thumb Right" +msgstr "Cursore di scorrimento orizzontale - Sposta a destra il cursore attivo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:238 +msgid "Horizontal slider - Move current thumb Left" +msgstr "Cursore di scorrimento orizzontale: sposta il cursore corrente a sinistra" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:239 +msgid "Horizontal slider - Move current thumb Right" +msgstr "Cursore di scorrimento orizzontale: sposta il cursore corrente a destra" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:236 +msgid "Horizontal slider - Set left thumb as active" +msgstr "Cursore di scorrimento orizzontale - Imposta cursore sinistro come attivo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:236 +msgid "Horizontal slider - Set left thumb to current thumb" +msgstr "Cursore di scorrimento orizzontale: sposta il cursore sinistro al cursore corrente" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:237 +msgid "Horizontal slider - Set right thumb as active" +msgstr "Cursore di scorrimento orizzontale - Imposta cursore destro come attivo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:237 +msgid "Horizontal slider - Set right thumb to current thumb" +msgstr "Cursore di scorrimento orizzontale: Imposta il cursore destro al cursore corrente" + #: src/libslic3r/PrintConfig.cpp:279 msgid "Horizontal width of the brim that will be printed around each object on the first layer." msgstr "Larghezza orizzontale del brim che sarà stampata attorno ad ogni oggetto nel primo layer." @@ -3877,9 +3937,7 @@ msgid "Hostname, IP or URL" msgstr "Nome Host, IP o URL" #: src/slic3r/GUI/Tab.cpp:210 -msgid "" -"Hover the cursor over buttons to find more information \n" -"or click this button." +msgid "Hover the cursor over buttons to find more information \nor click this button." msgstr "Scorri il cursore sui bottoni per ottenere maggiori informazioni o clicca su questo bottone." #: src/libslic3r/PrintConfig.cpp:2976 @@ -3906,6 +3964,10 @@ msgstr "Come applicare i limiti" msgid "How to apply the Machine Limits" msgstr "Come applicare i Limiti Macchina" +#: src/libslic3r/PrintConfig.cpp:163 +msgid "HTTP digest" +msgstr "HTTP digest" + #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:358 #: src/libslic3r/PrintConfig.cpp:113 msgid "HTTPS CA File" @@ -3928,12 +3990,12 @@ msgid "If checked, supports will be generated automatically based on the overhan msgstr "Se attivo, verranno automaticamente generati i supporti in base al valore soglia di sporgenza. Se disattivato, i supporti verranno generati solamente all'interno dei volumi di \"Rinforzo Supporto\"." #: src/slic3r/GUI/ConfigWizard.cpp:1132 -#, c-format +#, possible-c-format msgid "If enabled, %s checks for new application versions online. When a new version becomes available, a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done." msgstr "Se attivato, %s verifica la presenza di nuove versioni online. Quando è disponibile una nuova versione, viene mostrata una notifica al successivo avvio dell'applicazione (mai durante l'uso del programma). È solo un meccanismo di notifica, non viene effettuato nessun aggiornamento automatico." #: src/slic3r/GUI/ConfigWizard.cpp:1142 -#, c-format +#, possible-c-format msgid "If enabled, %s downloads updates of built-in system presets in the background.These updates are downloaded into a separate temporary location.When a new preset version becomes available it is offered at application startup." msgstr "Se attivo, %s scarica in background gli aggiornamenti dei preset integrati nel sistema. Questi aggiornamenti vengono scaricati in una cartella temporanea separata. Quando è disponibile una nuova versione del preset, questa viene proposta all'avvio." @@ -3942,12 +4004,8 @@ msgid "If enabled, all printing extruders will be primed at the front edge of th msgstr "Se attivata, tutti gli estrusori di stampa verranno preparati nel bordo frontale del piano di stampa all'inizio della stampa." #: src/slic3r/GUI/ConfigWizard.cpp:1164 -msgid "" -"If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" -"If not enabled, the Reload from disk command will ask to select each file using an open file dialog." -msgstr "" -"Se attivo, permette al comando di Ricarica da disco di trovare e caricare automaticamente i file quando richiesti.\n" -"Se non attivo, il comando Ricarica da disco chiederà di selezionare ciascun file tramite finestra di apertura file." +msgid "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\nIf not enabled, the Reload from disk command will ask to select each file using an open file dialog." +msgstr "Se attivo, permette al comando di Ricarica da disco di trovare e caricare automaticamente i file quando richiesti.\nSe non attivo, il comando Ricarica da disco chiederà di selezionare ciascun file tramite finestra di apertura file." #: src/slic3r/GUI/Preferences.cpp:91 msgid "If enabled, allows the Reload from disk command to automatically find and load the files when invoked." @@ -3969,6 +4027,18 @@ msgstr "Se abilitato, renderizza l'oggetto utilizzando la mappa ambientale." msgid "If enabled, reverses the direction of zoom with mouse wheel" msgstr "Se abilitato, inverte la direzione dello zoom con la rotella del mouse" +#: src/slic3r/GUI/Preferences.cpp:93 +msgid "If enabled, sets PrusaSlicer as default application to open .3mf files." +msgstr "Se abilitato, imposta PrusaSlicer come applicazione predefinita per aprire i file .3mf." + +#: src/slic3r/GUI/Preferences.cpp:100 +msgid "If enabled, sets PrusaSlicer as default application to open .stl files." +msgstr "Se abilitato, imposta PrusaSlicer come applicazione predefinita per aprire i file .stl." + +#: src/slic3r/GUI/Preferences.cpp:179 +msgid "If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files." +msgstr "Se abilitato, imposta il visualizzatore G-code di PrusaSlicer come applicazione predefinita per aprire i file .gcode." + #: src/slic3r/GUI/Preferences.cpp:99 msgid "If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup." msgstr "Se abilitato, Slic3r scarica gli aggiornamenti dei preset inclusi in background. Questi aggiornamenti sono scaricati in una posizione temporanea. Quando una nuova versione dei preset diventa disponibile, viene offerta all'avvio." @@ -3985,6 +4055,14 @@ msgstr "Se abilitato, il pulsante per ridurre la barra di scorrimento laterale a msgid "If enabled, the command line arguments are sent to an existing instance of GUI PrusaSlicer, or an existing PrusaSlicer window is activated. Overrides the \"single_instance\" configuration value from application preferences." msgstr "Se abilitato, gli argomenti della riga di comando vengono inviati ad un'istanza GUI esistente di PrusaSlicer, oppure viene attivata una finestra PrusaSlicer esistente. Sovrascrive il valore di configurazione \"single_instance\" dalle preferenze dell'applicazione." +#: src/slic3r/GUI/Preferences.cpp:278 +msgid "If enabled, the descriptions of configuration parameters in settings tabs woldn't work as hyperlinks. If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks." +msgstr "Se abilitata, le descrizioni dei parametri di configurazione nelle schede delle impostazioni non funzionano come collegamenti ipertestuali. Se disabilitata, le descrizioni dei parametri di configurazione nelle schede delle impostazioni funzioneranno come collegamenti ipertestuali." + +#: src/slic3r/GUI/Preferences.cpp:209 +msgid "If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M" +msgstr "Se abilitata, la finestra di dialogo delle impostazioni dei dispositivi 3DConnexion è disponibile premendo CTRL+M" + #: src/libslic3r/PrintConfig.cpp:1804 msgid "If enabled, the skirt will be as tall as a highest printed object. This is useful to protect an ABS or ASA print from warping and detaching from print bed due to wind draft." msgstr "Se abilitata, lo skirt sarà alto quanto l'oggetto stampato più alto. Questo è utile per evitare che una stampa ABS o ASA si deformi e si stacchi dal piano di stampa a causa di correnti d'aria." @@ -4098,10 +4176,18 @@ msgstr "Importa Configurazione da &progetto" msgid "Import Config from ini/amf/3mf/gcode" msgstr "Importa Config da ini/amf/3mf/gcode" +#: src/slic3r/GUI/Plater.cpp:1419 +msgid "Import config only" +msgstr "Importa solo configurazione" + #: src/slic3r/GUI/Jobs/SLAImportJob.cpp:39 msgid "Import file" msgstr "Importa file" +#: src/slic3r/GUI/Plater.cpp:1418 +msgid "Import geometry only" +msgstr "Importa solo la geometria" + #: src/slic3r/GUI/Jobs/SLAImportJob.cpp:46 msgid "Import model and profile" msgstr "Importa modello e profilo" @@ -4166,7 +4252,7 @@ msgid "in" msgstr "in" #: src/slic3r/GUI/GUI_ObjectList.cpp:3885 -#, c-format +#, possible-c-format msgid "In this mode you can select only other %s Items%s" msgstr "In questa modalità puoi selezionare solo altri %s oggetti %s" @@ -4179,7 +4265,7 @@ msgid "Incompatible presets" msgstr "Preset incompatibili" #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:75 -#, c-format +#, possible-c-format msgid "Incompatible with this %s" msgstr "Incompatibile con questo %s" @@ -4193,12 +4279,8 @@ msgstr "Aumenta/diminuisci l'area di modifica" #. TRN Description for "UNLOCKED LOCK" #: src/slic3r/GUI/Tab.cpp:3695 -msgid "" -"indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\n" -"Click the UNLOCKED LOCK icon to reset all settings for current option group to the system (or default) values." -msgstr "" -"indica che è stata modificata qualche impostazione e non è uguale ai valori di sistema (o predefiniti) del corrente gruppo di opzioni.\n" -"Clicca l'icona LUCCHETTO APERTO per reimpostare tutte le impostazioni del corrente gruppo di opzioni ai valori di sistema (o predefiniti)." +msgid "indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\nClick the UNLOCKED LOCK icon to reset all settings for current option group to the system (or default) values." +msgstr "indica che è stata modificata qualche impostazione e non è uguale ai valori di sistema (o predefiniti) del corrente gruppo di opzioni.\nClicca l'icona LUCCHETTO APERTO per reimpostare tutte le impostazioni del corrente gruppo di opzioni ai valori di sistema (o predefiniti)." #. TRN Description for "LOCKED LOCK" #: src/slic3r/GUI/Tab.cpp:3691 @@ -4207,12 +4289,8 @@ msgstr "indica che le impostazioni sono uguali ai valori di sistema (o predefini #. TRN Description for "BACK ARROW" #: src/slic3r/GUI/Tab.cpp:3707 -msgid "" -"indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" -"Click the BACK ARROW icon to reset all settings for the current option group to the last saved preset." -msgstr "" -"indica che le impostazioni sono state modificate e non corrispondono all'ultimo preset salvato per l'attuale gruppo opzioni.\n" -"Clicca l'icona FRECCIA INDIETRO per reimpostare all'ultimo preset salvato tutte le impostazioni per il seguente gruppo di opzioni." +msgid "indicates that the settings were changed and are not equal to the last saved preset for the current option group.\nClick the BACK ARROW icon to reset all settings for the current option group to the last saved preset." +msgstr "indica che le impostazioni sono state modificate e non corrispondono all'ultimo preset salvato per l'attuale gruppo opzioni.\nClicca l'icona FRECCIA INDIETRO per reimpostare all'ultimo preset salvato tutte le impostazioni per il seguente gruppo di opzioni." #: src/slic3r/GUI/ConfigManipulation.cpp:210 #: src/slic3r/GUI/GUI_ObjectList.cpp:35 src/slic3r/GUI/GUI_ObjectList.cpp:93 @@ -4287,7 +4365,7 @@ msgstr "Ispeziona / attiva istantanee di configurazione" #: src/slic3r/GUI/ObjectDataViewModel.cpp:62 #: src/slic3r/GUI/ObjectDataViewModel.cpp:218 -#, c-format +#, possible-c-format msgid "Instance %d" msgstr "Istanza %d" @@ -4433,10 +4511,30 @@ msgstr "Jitter" msgid "Jump to height" msgstr "Salta all'altezza" +#: src/slic3r/GUI/DoubleSlider.cpp:1223 +#, possible-c-format +msgid "Jump to height %s\nor Set ruler mode" +msgstr "Vai all'altezza %s \no Imposta la modalità righello" + +#: src/slic3r/GUI/DoubleSlider.cpp:1220 +#, possible-c-format +msgid "Jump to height %s\nSet ruler mode\nor Set extruder sequence for the entire print" +msgstr "Vai all'altezza %s\nImposta la modalità del righello\no Imposta la sequenza dell'estrusore per l'intera stampa" + #: src/slic3r/GUI/DoubleSlider.cpp:1075 -#, c-format +#, possible-c-format msgid "Jump to height %s or Set extruder sequence for the entire print" -msgstr "Salta all'altezza %s o Imposta sequenza estrusore per l'intera stampa" +msgstr "Vai all'altezza %s o Imposta sequenza estrusore per l'intera stampa" + +#: src/slic3r/GUI/DoubleSlider.cpp:1222 +#, possible-c-format +msgid "Jump to height %s or Set ruler mode" +msgstr "Vai all'altezza %s o Imposta la modalità righello" + +#: src/slic3r/GUI/DoubleSlider.cpp:1220 +#, possible-c-format +msgid "Jump to height %s Set ruler mode\n or Set extruder sequence for the entire print" +msgstr "Vai all'altezza %s Imposta la modalità del righello\n o Imposta la sequenza dell'estrusore per l'intera stampa" #: src/slic3r/GUI/DoubleSlider.cpp:1071 src/slic3r/GUI/DoubleSlider.cpp:1852 msgid "Jump to move" @@ -4572,7 +4670,7 @@ msgstr "Chiusura supporti Paint-on" #: src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp:47 msgid "Leaving Seam painting" -msgstr "Lasciare pittura Giunzione" +msgstr "Lasciare Pittura Giunzione" #: src/slic3r/GUI/MainFrame.cpp:968 src/slic3r/GUI/MainFrame.cpp:1288 msgid "Left" @@ -4613,6 +4711,10 @@ msgstr "Lunghezza" msgid "Length of the cooling tube to limit space for cooling moves inside it." msgstr "Lunghezza del tubo di raffreddamento per limitare lo spazio delle mosse di raffreddamento al suo interno." +#: src/libslic3r/PrintConfig.cpp:1068 +msgid "Length of the infill anchor" +msgstr "Lunghezza dell'ancoraggio del riempimento" + #. TRN "Slic3r _is licensed under the_ License" #: src/slic3r/GUI/AboutDialog.cpp:141 msgid "License agreements of all following programs (libraries) are part of application license agreement" @@ -4666,6 +4768,14 @@ msgstr "Carica configurazione dal file specificato. Può essere usato più di un msgid "Load exported configuration file" msgstr "Carica un file di configurazione esportato" +#: src/slic3r/GUI/Plater.cpp:1543 src/slic3r/GUI/Plater.cpp:4976 +msgid "Load File" +msgstr "Carica file" + +#: src/slic3r/GUI/Plater.cpp:1548 src/slic3r/GUI/Plater.cpp:4981 +msgid "Load Files" +msgstr "Carica file" + #: src/slic3r/GUI/GUI_ObjectList.cpp:2038 msgid "Load Part" msgstr "Carica Parte" @@ -4761,6 +4871,10 @@ msgstr "Giri (minimo)" msgid "Lower Layer" msgstr "Layer Inferiore" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:219 +msgid "Lower layer" +msgstr "Layer inferiore" + #: src/slic3r/GUI/Tab.cpp:2346 src/slic3r/GUI/Tab.cpp:2442 #: src/libslic3r/PrintConfig.cpp:1202 src/libslic3r/PrintConfig.cpp:1237 #: src/libslic3r/PrintConfig.cpp:1254 src/libslic3r/PrintConfig.cpp:1271 @@ -4993,6 +5107,10 @@ msgstr "Jerk massimo Y" msgid "Maximum jerk Z" msgstr "Jerk massimo Z" +#: src/libslic3r/PrintConfig.cpp:1095 +msgid "Maximum length of the infill anchor" +msgstr "Lunghezza massima dell'ancoraggio del riempimento" + #: src/libslic3r/PrintConfig.cpp:2814 msgid "Maximum number of bridges that can be placed on a pillar. Bridges hold support point pinheads and connect to pillars as small branches." msgstr "Numero massimo di ponteggi che può essere posizionato su un pilastro. I ponteggi mantengono le capocchie dei punti di supporto e si collegano ai pilastri come piccoli rami." @@ -5168,7 +5286,7 @@ msgid "Mirror vertically" msgstr "Specchia verticalmente" #: src/slic3r/Utils/AstroBox.cpp:69 src/slic3r/Utils/OctoPrint.cpp:68 -#, c-format +#, possible-c-format msgid "Mismatched type of print host: %s" msgstr "Tipo di Host di stampa non corrispondente: %s" @@ -5368,6 +5486,14 @@ msgstr "Rotella del mouse:" msgid "Move" msgstr "Sposta" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:255 +msgid "Move active slider thumb Left" +msgstr "Sposta il cursore di scorrimento attivo sinistro" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:256 +msgid "Move active slider thumb Right" +msgstr "Sposta cursore di scorrimento attivo destro" + #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1219 msgid "Move clipping plane" msgstr "Sposta piano sezione" @@ -5392,6 +5518,16 @@ msgstr "Solleva la barra di scorrimento attuale" msgid "Move drainage hole" msgstr "Sposta foro di drenaggio" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:209 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:213 +msgid "Move horizontal slider current thumb Left" +msgstr "Sposta il cursore di scorrimento orizzontale corrente a Sinistra" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:210 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:214 +msgid "Move horizontal slider current thumb Right" +msgstr "Sposta il cursore di scorrimento orizzontale corrente a Destra" + #: src/slic3r/GUI/GLCanvas3D.cpp:3810 msgid "Move Object" msgstr "Sposta oggetto" @@ -5420,6 +5556,16 @@ msgstr "Sposta selezione 10 mm in direzione Y positiva" msgid "Move support point" msgstr "Sposta punto di supporto" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:208 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:212 +msgid "Move vertical slider current thumb Down" +msgstr "Sposta il cursore di scorrimento verticale corrente in Basso" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:207 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:211 +msgid "Move vertical slider current thumb Up" +msgstr "Sposta il cursore di scorrimento verticale corrente verso l'alto" + #: src/slic3r/GUI/GCodeViewer.cpp:2492 msgid "Movement" msgstr "Movimento" @@ -5441,7 +5587,7 @@ msgid "Multi-part object detected" msgstr "Rilevato oggetto in parti multiple" #: src/slic3r/GUI/FirmwareDialog.cpp:419 src/slic3r/GUI/FirmwareDialog.cpp:454 -#, c-format +#, possible-c-format msgid "Multiple %s devices found. Please only connect one at a time for flashing." msgstr "Trovati molteplici %s dispositivi. Per favore connettine uno alla volta per il flashing." @@ -5450,13 +5596,8 @@ msgid "Multiple Extruders" msgstr "Estrusori multipli" #: src/slic3r/GUI/Plater.cpp:2372 -msgid "" -"Multiple objects were loaded for a multi-material printer.\n" -"Instead of considering them as multiple objects, should I consider\n" -"these files to represent a single object having multiple parts?" -msgstr "" -"Sono stati caricati oggetti multipli per stampante multi-material.\n" -"Invece di considerarli come oggetti multipli, devo considerarli come parte di un singolo oggetto avente parti multiple?" +msgid "Multiple objects were loaded for a multi-material printer.\nInstead of considering them as multiple objects, should I consider\nthese files to represent a single object having multiple parts?" +msgstr "Sono stati caricati oggetti multipli per stampante multi-material.\nInvece di considerarli come oggetti multipli, devo considerarli come parte di un singolo oggetto avente parti multiple?" #: src/libslic3r/PrintConfig.cpp:3638 msgid "Multiply copies by creating a grid." @@ -5525,7 +5666,7 @@ msgid "New version is available." msgstr "È disponibile una nuova versione." #: src/slic3r/GUI/UpdateDialogs.cpp:38 -#, c-format +#, possible-c-format msgid "New version of %s is available" msgstr "È disponibile una nuova versione di %s" @@ -5608,14 +5749,8 @@ msgid "Note, that this/those printer(s) will be deleted after deleting of the se msgstr "Nota: questa/e stampante/i saranno cancellate dopo aver cancellato il preset selezionato." #: src/slic3r/GUI/Tab.cpp:2039 -msgid "" -"Note: All parameters from this group are moved to the Physical Printer settings (see changelog).\n" -"\n" -"A new Physical Printer profile is created by clicking on the \"cog\" icon right of the Printer profiles combo box, by selecting the \"Add physical printer\" item in the Printer combo box. The Physical Printer profile editor opens also when clicking on the \"cog\" icon in the Printer settings tab. The Physical Printer profiles are being stored into PrusaSlicer/physical_printer directory." -msgstr "" -"Nota: Tutti i parametri di questo gruppo vengono spostati nelle impostazioni della stampante fisica (vedi changelog).\n" -"\n" -"Un nuovo profilo di stampante fisica viene creato cliccando sull'icona \"ingranaggio\" a destra della casella combinata dei profili della stampante, selezionando la voce \"Aggiungi stampante fisica\" nella casella combinata della stampante. L'editor dei profili della stampante fisica si apre anche cliccando sull'icona \"ingranaggio\" nella scheda Impostazioni della stampante. I profili della stampante fisica vengono memorizzati nella directory PrusaSlicer/physical_printer." +msgid "Note: All parameters from this group are moved to the Physical Printer settings (see changelog).\n\nA new Physical Printer profile is created by clicking on the \"cog\" icon right of the Printer profiles combo box, by selecting the \"Add physical printer\" item in the Printer combo box. The Physical Printer profile editor opens also when clicking on the \"cog\" icon in the Printer settings tab. The Physical Printer profiles are being stored into PrusaSlicer/physical_printer directory." +msgstr "Nota: Tutti i parametri di questo gruppo vengono spostati nelle impostazioni della stampante fisica (vedi changelog).\n\nUn nuovo profilo di stampante fisica viene creato cliccando sull'icona \"ingranaggio\" a destra della casella combinata dei profili della stampante, selezionando la voce \"Aggiungi stampante fisica\" nella casella combinata della stampante. L'editor dei profili della stampante fisica si apre anche cliccando sull'icona \"ingranaggio\" nella scheda Impostazioni della stampante. I profili della stampante fisica vengono memorizzati nella directory PrusaSlicer/physical_printer." #: src/slic3r/Utils/AstroBox.cpp:92 msgid "Note: AstroBox version at least 1.1.0 is required." @@ -5804,10 +5939,14 @@ msgid "On OSX there is always only one instance of app running by default. Howev msgstr "Su OSX per impostazione predefinita c'è sempre una sola istanza di applicazione in esecuzione. Tuttavia è possibile eseguire più istanze della stessa app dalla riga di comando. In tal caso questa impostazione consentirà una sola istanza." #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:359 -#, c-format +#, possible-c-format msgid "On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain." msgstr "Su questo sistema, %s utilizza certificati HTTPS provenienti dal sistema Certificate Store o da Keychain." +#: src/slic3r/GUI/KBShortcutsDialog.cpp:215 +msgid "On/Off one layer mode of the vertical slider" +msgstr "On/Off modalità un layer del cursore di scorrimento verticale" + #: src/slic3r/GUI/DoubleSlider.cpp:1064 msgid "One layer mode" msgstr "Modalità Un Layer" @@ -5869,6 +6008,10 @@ msgstr "Apri una nuova istanza PrusaSlicer" msgid "Open a project file" msgstr "Apri un file progetto" +#: src/slic3r/GUI/Plater.cpp:1417 +msgid "Open as project" +msgstr "Apri come progetto" + #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:330 msgid "Open CA certificate file" msgstr "Apri file di certificato CA" @@ -5911,7 +6054,7 @@ msgid "Open PrusaSlicer" msgstr "Apri PrusaSlicer" #: src/slic3r/GUI/MainFrame.cpp:918 src/slic3r/GUI/MainFrame.cpp:1317 -#, c-format +#, possible-c-format msgid "Open the %s website in your browser" msgstr "Apri il sito web di %s nel browser" @@ -6213,9 +6356,9 @@ msgid "Physical printers" msgstr "Stampanti fisiche" #: src/slic3r/GUI/ConfigWizard.cpp:1226 -#, c-format +#, possible-c-format msgid "Pick another vendor supported by %s" -msgstr "Scegli un altro distributore supportato da %s" +msgstr "Scegli un altro produttore supportato da %s" #: src/libslic3r/PrintConfig.cpp:66 msgid "Picture sizes to be stored into a .gcode and .sl1 files" @@ -6345,6 +6488,10 @@ msgstr "Il preset \"%1%\" non è compatibile con il nuovo profilo stampante e po msgid "Preset with name \"%1%\" already exists and is imcopatible with selected printer." msgstr "Il preset con il nome \"%1%\" esiste già e non è compatibile con la stampante selezionata." +#: src/slic3r/GUI/SavePresetDialog.cpp:136 +msgid "Preset with name \"%1%\" already exists and is incopatible with selected printer." +msgstr "Il preset con il nome \"%1%\" è già esistente ed è incompatibile con la stampante selezionata." + #: src/slic3r/GUI/SavePresetDialog.cpp:148 msgid "Preset with name \"%1%\" already exists." msgstr "Preset con il nome \"%1%\" già esistente." @@ -6363,23 +6510,15 @@ msgid "Press to activate selection rectangle" msgstr "Premi per attivare il rettangolo di selezione" #: src/slic3r/GUI/KBShortcutsDialog.cpp:155 -msgid "" -"Press to select multiple objects\n" -"or move multiple objects with mouse" -msgstr "" -"Premi per selezionare o spostare\n" -"oggetti multipli con il mouse" +msgid "Press to select multiple objects\nor move multiple objects with mouse" +msgstr "Premi per selezionare o spostare\noggetti multipli con il mouse" #: src/slic3r/GUI/KBShortcutsDialog.cpp:221 #: src/slic3r/GUI/KBShortcutsDialog.cpp:222 #: src/slic3r/GUI/KBShortcutsDialog.cpp:231 #: src/slic3r/GUI/KBShortcutsDialog.cpp:232 -msgid "" -"Press to speed up 5 times while moving thumb\n" -"with arrow keys or mouse wheel" -msgstr "" -"Premere per accelerare di 5 volte durante il movimento del cursore\n" -"con i tasti freccia o la rotella del mouse" +msgid "Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel" +msgstr "Premere per accelerare di 5 volte durante il movimento del cursore\ncon i tasti freccia o la rotella del mouse" #: src/slic3r/GUI/KBShortcutsDialog.cpp:212 src/slic3r/GUI/Plater.cpp:4052 #: src/slic3r/GUI/Tab.cpp:2559 @@ -6534,7 +6673,7 @@ msgstr "Stampa con più estrusori con ugelli di di diametro diverso. Se il suppo #. TRN "Processing input_file_basename" #: src/slic3r/GUI/MainFrame.cpp:1550 -#, c-format +#, possible-c-format msgid "Processing %s" msgstr "Elaborando %s" @@ -6589,10 +6728,8 @@ msgid "PrusaSlicer is closing: Unsaved Changes" msgstr "Chiusura PrusaSlicer: Modifiche non salvate" #: src/slic3r/GUI/OpenGLManager.cpp:259 -#, c-format -msgid "" -"PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" -"while OpenGL version %s, render %s, vendor %s was detected." +#, possible-c-format +msgid "PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \nwhile OpenGL version %s, render %s, vendor %s was detected." msgstr "PrusaSlicer richiede un driver video con supporto OpenGL 2.0 per funzionare correttamente, mentre è stata rilevata la versione %s OpenGL, render %s, distributore %s." #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:50 @@ -6604,14 +6741,8 @@ msgid "PrusaSlicer will remember your action." msgstr "PrusaSlicer ricorderà la tua azione." #: src/slic3r/GUI/ConfigWizard.cpp:1174 -msgid "" -"PrusaSlicer's user interfaces comes in three variants:\n" -"Simple, Advanced, and Expert.\n" -"The Simple mode shows only the most frequently used settings relevant for regular 3D printing. The other two offer progressively more sophisticated fine-tuning, they are suitable for advanced and expert users, respectively." -msgstr "" -"L'interfaccia utente di PrusaSlicer è disponibile in tre varianti:\n" -"Semplice, Avanzata ed Esperto.\n" -"La modalità Semplice mostra solo le impostazioni rilevanti utilizzate più spesso per una semplice stampa 3D. Le altre due offrono progressivamente ottimizzazioni più sofisticate, sono adatte ad utenti avanzati ed esperti, rispettivamente." +msgid "PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\nThe Simple mode shows only the most frequently used settings relevant for regular 3D printing. The other two offer progressively more sophisticated fine-tuning, they are suitable for advanced and expert users, respectively." +msgstr "L'interfaccia utente di PrusaSlicer è disponibile in tre varianti:\nSemplice, Avanzata ed Esperto.\nLa modalità Semplice mostra solo le impostazioni rilevanti utilizzate più spesso per una semplice stampa 3D. Le altre due offrono progressivamente ottimizzazioni più sofisticate, sono adatte ad utenti avanzati ed esperti, rispettivamente." #: src/slic3r/GUI/UnsavedChangesDialog.cpp:668 msgid "PrusaSlicer: Don't ask me again" @@ -6657,7 +6788,7 @@ msgstr "Veloce" #: src/slic3r/GUI/GUI_ObjectList.cpp:1661 #: src/slic3r/GUI/GUI_ObjectList.cpp:1667 #: src/slic3r/GUI/GUI_ObjectList.cpp:2008 -#, c-format +#, possible-c-format msgid "Quick Add Settings (%s)" msgstr "Aggiungere Impostazioni Rapide (%s)" @@ -6670,7 +6801,7 @@ msgid "Quick Slice and Save As" msgstr "Slice veloce e Salva Come" #: src/slic3r/GUI/MainFrame.cpp:1144 src/slic3r/GUI/MainFrame.cpp:1402 -#, c-format +#, possible-c-format msgid "Quit %s" msgstr "Chiudi %s" @@ -6695,14 +6826,8 @@ msgid "Ramming customization" msgstr "Personalizzazione del ramming" #: src/slic3r/GUI/WipeTowerDialog.cpp:41 -msgid "" -"Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n" -"\n" -"This is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." -msgstr "" -"Il ramming è la rapida estrusione appena prima di un cambio di attrezzo in una stampante MM ad estrusore singolo. Lo scopo è di dare la forma corretta al capo del filamento scaricato cosicché non prevenga l'inserzione del nuovo filamento e perché possa essere inserito più facilmente esso stesso. Questa fase è importante e materiali diversi possono richiedere velocità diverse per ottenere la forma corretta. Per questo motivo le velocità di estrusione del ramming possono essere modificate.\n" -"\n" -"Questa è un'impostazione per esperti, valori scorretti produrranno facilmente dei blocchi, o porteranno l'ingranaggio di estrusione a macinare il filamento etc." +msgid "Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." +msgstr "Il ramming è la rapida estrusione appena prima di un cambio di attrezzo in una stampante MM ad estrusore singolo. Lo scopo è di dare la forma corretta al capo del filamento scaricato cosicché non prevenga l'inserzione del nuovo filamento e perché possa essere inserito più facilmente esso stesso. Questa fase è importante e materiali diversi possono richiedere velocità diverse per ottenere la forma corretta. Per questo motivo le velocità di estrusione del ramming possono essere modificate.\n\nQuesta è un'impostazione per esperti, valori scorretti produrranno facilmente dei blocchi, o porteranno l'ingranaggio di estrusione a macinare il filamento etc." #: src/slic3r/GUI/WipeTowerDialog.cpp:91 msgid "Ramming line spacing" @@ -6762,7 +6887,7 @@ msgid "Recent projects" msgstr "Prog&etti recenti" #: src/slic3r/GUI/PresetHints.cpp:262 -#, c-format +#, possible-c-format msgid "Recommended object thin wall thickness for layer height %.2f and" msgstr "Spessore raccomandato per oggetto con parete sottile per altezza layer %.2f e" @@ -6797,7 +6922,7 @@ msgid "Redo" msgstr "Ripeti" #: src/slic3r/GUI/GLCanvas3D.cpp:4382 -#, c-format +#, possible-c-format msgid "Redo %1$d Action" msgid_plural "Redo %1$d Actions" msgstr[0] "Ripeti %1$d Azione" @@ -7021,22 +7146,22 @@ msgid "Report an I&ssue" msgstr "&Segnala un problema" #: src/slic3r/GUI/MainFrame.cpp:928 src/slic3r/GUI/MainFrame.cpp:1327 -#, c-format +#, possible-c-format msgid "Report an issue on %s" msgstr "Segnala un problema su %s" #: src/slic3r/Utils/PresetUpdater.cpp:733 -#, c-format +#, possible-c-format msgid "requires max. %s" msgstr "richiede max. %s" #: src/slic3r/Utils/PresetUpdater.cpp:730 -#, c-format +#, possible-c-format msgid "requires min. %s" msgstr "richiede min. %s" #: src/slic3r/Utils/PresetUpdater.cpp:726 -#, c-format +#, possible-c-format msgid "requires min. %s and max. %s" msgstr "richiede min. %s e max. %s" @@ -7220,8 +7345,12 @@ msgstr "Angolo di rotazione sull'asse Y in gradi." msgid "Rotation angle around the Z axis in degrees." msgstr "Angolo di rotazione attorno all'asse Z in gradi." +#: src/slic3r/GUI/DoubleSlider.cpp:1836 +msgid "Ruler mode" +msgstr "Modalità righello" + #: src/slic3r/GUI/GUI_App.cpp:1474 -#, c-format +#, possible-c-format msgid "Run %s" msgstr "Run %s" @@ -7253,12 +7382,12 @@ msgid "Save" msgstr "Salva" #: src/slic3r/GUI/SavePresetDialog.cpp:72 -#, c-format +#, possible-c-format msgid "Save %s as:" msgstr "Salva %s come:" #: src/slic3r/GUI/MainFrame.cpp:1527 -#, c-format +#, possible-c-format msgid "Save %s file as:" msgstr "Salva file %s come:" @@ -7280,7 +7409,7 @@ msgstr "Salva configurazione nel file specificato." #. TRN "Save current Settings" #: src/slic3r/GUI/Tab.cpp:203 -#, c-format +#, possible-c-format msgid "Save current %s" msgstr "Salva le %s attuali" @@ -7393,7 +7522,7 @@ msgstr "Pittura giunzione" #: src/libslic3r/PrintConfig.cpp:1729 msgid "Seam position" -msgstr "Posizione giunzioni" +msgstr "Posizione giunzione" #: src/libslic3r/PrintConfig.cpp:1750 msgid "Seam preferred direction" @@ -7456,6 +7585,10 @@ msgstr "Seleziona tutti i punti" msgid "Select all standard printers" msgstr "Seleziona tutte le stampanti standard" +#: src/slic3r/GUI/Plater.cpp:1422 +msgid "Select an action to apply to the file" +msgstr "Seleziona un'azione da applicare al file" + #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1215 msgid "Select by rectangle" msgstr "Seleziona con rettangolo" @@ -7525,14 +7658,8 @@ msgid "Select what kind of support do you need" msgstr "Seleziona il tipo di supporto richiesto" #: src/slic3r/GUI/DoubleSlider.cpp:2135 -msgid "" -"Select YES if you want to delete all saved tool changes, \n" -"NO if you want all tool changes switch to color changes, \n" -"or CANCEL to leave it unchanged." -msgstr "" -"Seleziona SI se vuoi cancellare tutti i cambi attrezzo salvati,\n" -"NO se vuoi che tutti i cambi attrezzo passino a cambi colore,\n" -"o ANNULLA per lasciarlo invariato." +msgid "Select YES if you want to delete all saved tool changes, \nNO if you want all tool changes switch to color changes, \nor CANCEL to leave it unchanged." +msgstr "Seleziona SI se vuoi cancellare tutti i cambi attrezzo salvati,\nNO se vuoi che tutti i cambi attrezzo passino a cambi colore,\no ANNULLA per lasciarlo invariato." #: src/slic3r/GUI/Selection.cpp:191 msgid "Selection-Add" @@ -7672,7 +7799,7 @@ msgid "Set number of instances" msgstr "Imposta numero di istanze" #: src/slic3r/GUI/Plater.cpp:4860 -#, c-format +#, possible-c-format msgid "Set numbers of copies to %d" msgstr "Imposta il numero di copie a %d" @@ -7692,6 +7819,10 @@ msgstr "Imposta stampabile" msgid "Set Printable Instance" msgstr "Imposta Istanza Stampabile" +#: src/slic3r/GUI/DoubleSlider.cpp:1836 +msgid "Set ruler mode" +msgstr "Imposta la modalità righello" + #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:893 msgid "Set Scale" msgstr "Imposta scala" @@ -7761,12 +7892,8 @@ msgid "Set upper thumb to current slider thumb" msgstr "Imposta il cursore superiore alla barra di scorrimento attuale" #: src/libslic3r/PrintConfig.cpp:3714 -msgid "" -"Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\n" -"For example. loglevel=2 logs fatal, error and warning level messages." -msgstr "" -"Imposta la sensibilità di log. 0:fatale, 1:errore, 2:avviso, 3:informazioni, 4:debug, 5:traccia\n" -"Per esempio. loglevel=2 registra messaggi fatali, di errore e di avviso." +msgid "Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\nFor example. loglevel=2 logs fatal, error and warning level messages." +msgstr "Imposta la sensibilità di log. 0:fatale, 1:errore, 2:avviso, 3:informazioni, 4:debug, 5:traccia\nPer esempio. loglevel=2 registra messaggi fatali, di errore e di avviso." #: src/slic3r/GUI/BedShapeDialog.cpp:292 src/slic3r/GUI/MainFrame.cpp:1969 msgid "Settings" @@ -7843,10 +7970,22 @@ msgstr "Mostra la finestra di informazioni" msgid "Show advanced settings" msgstr "Mostra impostazioni avanzate" +#: src/slic3r/GUI/Preferences.cpp:120 +msgid "Show drop project dialog" +msgstr "Mostra la finestra di rilascio del progetto" + #: src/slic3r/GUI/PrintHostDialogs.cpp:157 msgid "Show error message" msgstr "Mostra messaggio d'errore" +#: src/slic3r/GUI/DoubleSlider.cpp:1832 +msgid "Show estimated print time" +msgstr "Mostra il tempo di stampa stimato" + +#: src/slic3r/GUI/DoubleSlider.cpp:1832 +msgid "Show estimated print time on the ruler" +msgstr "Mostra il tempo di stampa stimato sul righello" + #: src/slic3r/GUI/Preferences.cpp:112 msgid "Show incompatible print and filament presets" msgstr "Mostra preset di stampa e di filamento incompatibili" @@ -7859,6 +7998,14 @@ msgstr "Mostra elenco scorciatoie di tastiera" msgid "Show normal mode" msgstr "Mostra modalità normale" +#: src/slic3r/GUI/DoubleSlider.cpp:1828 +msgid "Show object height" +msgstr "Mostra l'altezza dell'oggetto" + +#: src/slic3r/GUI/DoubleSlider.cpp:1828 +msgid "Show object height on the ruler" +msgstr "Mostra l'altezza dell'oggetto sul righello" + #: src/slic3r/GUI/MainFrame.cpp:1294 msgid "Show object/instance labels in 3D scene" msgstr "Mostra nella scena 3D le etichette dell'oggetto/istanza" @@ -7935,10 +8082,18 @@ msgstr "Mostra cartella configurazione utente (datadir)" msgid "Show/Hide 3Dconnexion devices settings dialog" msgstr "Mostra/Nascondi finestra delle impostazioni dei dispositivi 3Dconnexion" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:175 +msgid "Show/Hide 3Dconnexion devices settings dialog, if enabled" +msgstr "Mostra/Nascondi finestra impostazioni dei dispositivi 3Dconnexion, se abilitata" + #: src/slic3r/GUI/KBShortcutsDialog.cpp:216 msgid "Show/Hide Legend & Estimated printing time" msgstr "Mostra/Nascondi Legenda & Stima del tempo di stampa" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:216 +msgid "Show/Hide Legend and Estimated printing time" +msgstr "Mostra/Nascondi Legenda e Stima del tempo di stampa" + #: src/slic3r/GUI/KBShortcutsDialog.cpp:141 msgid "Show/Hide object/instance labels" msgstr "Mostra/Nascondi etichette dell'oggetto/istanza" @@ -7964,14 +8119,8 @@ msgid "Single Extruder Multi Material" msgstr "Estrusore singolo Multi Material" #: src/slic3r/GUI/Tab.cpp:2101 -msgid "" -"Single Extruder Multi Material is selected, \n" -"and all extruders must have the same diameter.\n" -"Do you want to change the diameter for all extruders to first extruder nozzle diameter value?" -msgstr "" -"Materiale multiplo a singolo estrusore selezionato,\n" -"tutti gli estrusori devono avere lo stesso diametro.\n" -"Vuoi modificare il diametro di tutti gli estrusori al valore del diametro dell'ugello del primo estrusore?" +msgid "Single Extruder Multi Material is selected, \nand all extruders must have the same diameter.\nDo you want to change the diameter for all extruders to first extruder nozzle diameter value?" +msgstr "Materiale multiplo a singolo estrusore selezionato,\ntutti gli estrusori devono avere lo stesso diametro.\nVuoi modificare il diametro di tutti gli estrusori al valore del diametro dell'ugello del primo estrusore?" #: src/slic3r/GUI/Tab.cpp:2476 msgid "Single extruder multimaterial parameters" @@ -8080,6 +8229,10 @@ msgstr "Slic3r può caricare i file G-code su un host di stampa. Questo campo de msgid "Slic3r can upload G-code files to a printer host. This field should contain the hostname, IP address or URL of the printer host instance." msgstr "Slic3r può caricare i file G-code su un host della stampante. Questo campo dovrebbe contenere il nome host, l'indirizzo IP o URL dell'istanza dell'host della stampante." +#: src/libslic3r/PrintConfig.cpp:100 +msgid "Slic3r can upload G-code files to a printer host. This field should contain the hostname, IP address or URL of the printer host instance. Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL in the following format: https://username:password@your-octopi-address/" +msgstr "Slic3r può caricare i file G-code su una stampante host. Questo campo deve contenere il nome host, l'indirizzo IP o l'URL dell'istanza host della stampante. L'host di stampa dietro HAProxy con l'autenticazione di base abilitata è accessibile inserendo il nome utente e la password nell'URL nel seguente formato: https://username:password@il tuo indirizzo di octopi/" + #: src/libslic3r/PrintConfig.cpp:1407 msgid "Slic3r will not scale speed down below this speed." msgstr "Slic3r non rallenterà la velocità al di sotto di questa." @@ -8235,13 +8388,9 @@ msgid "Some G/M-code commands, including temperature control and others, are not msgstr "Alcuni comandi G/M-code, incluso il controllo di temperatura e altri, non sono universali. Imposta questa opzione nel firmware della tua stampante per ottenere un output compatibile. La versione \"No extrusion\" evita che PrusaSlicer non esporti alcun valore." #: src/slic3r/GUI/Plater.cpp:2309 -#, c-format -msgid "" -"Some object(s) in file %s looks like saved in inches.\n" -"Should I consider them as a saved in inches and convert them?" -msgstr "" -"Alcuni oggetti nel file %s sembrano salvati in pollici.\n" -"Devo considerarli come salvati in pollici e convertirli?" +#, possible-c-format +msgid "Some object(s) in file %s looks like saved in inches.\nShould I consider them as a saved in inches and convert them?" +msgstr "Alcuni oggetti nel file %s sembrano salvati in pollici.\nDevo considerarli come salvati in pollici e convertirli?" #: src/slic3r/GUI/GLCanvas3D.cpp:636 msgid "Some objects are not visible." @@ -8263,6 +8412,10 @@ msgstr "Per alcuni oggetti possono bastare pochi piccoli pad invece che un singo msgid "Some printers or printer setups may have difficulties printing with a variable layer height. Enabled by default." msgstr "Alcune stampanti o setup di stampanti possono riscontrare difficoltà a stampare con l'altezza layer variabile. Attivato come predefinito." +#: src/slic3r/GUI/GLCanvas3D.cpp:3967 +msgid "Spacing" +msgstr "Spaziatura " + #: src/libslic3r/PrintConfig.cpp:2126 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "Spaziatura tra le linee di interfaccia. Imposta a zero per ottenere un'interfaccia solida." @@ -8408,6 +8561,10 @@ msgstr "Dividi in parti" msgid "Split to Parts" msgstr "Dividi in parti" +#: src/libslic3r/PrintConfig.cpp:812 +msgid "Spool weight" +msgstr "Peso bobina" + #: src/slic3r/GUI/ConfigWizard.cpp:307 msgid "Standard" msgstr "Standard" @@ -8442,30 +8599,8 @@ msgid "Start the application" msgstr "Avvia l'applicazione" #: src/slic3r/GUI/GUI_App.cpp:386 -msgid "" -"Starting with %1% 2.3, configuration directory on Linux has changed (according to XDG Base Directory Specification) to \n" -"%2%.\n" -"\n" -"This directory did not exist yet (maybe you run the new version for the first time).\n" -"However, an old %1% configuration directory was detected in \n" -"%3%.\n" -"\n" -"Consider moving the contents of the old directory to the new location in order to access your profiles, etc.\n" -"Note that if you decide to downgrade %1% in future, it will use the old location again.\n" -"\n" -"What do you want to do now?" -msgstr "" -"A partire da %1% 2.3, la directory di configurazione su Linux è cambiata (secondo la XDG Base Directory Specification) in \n" -"%2%.\n" -"\n" -"Questa directory non esiste ancora (forse stai usando la nuova versione per la prima volta).\n" -"Tuttavia, è stata rilevata una vecchia directory di configurazione %1% in \n" -"%3%.\n" -"\n" -"Prendere in considerazione la possibilità di spostare il contenuto della vecchia directory nella nuova posizione per accedere ai propri profili, ecc.\n" -"Notare che se si decide di fare un downgrade di %1% in futuro, si utilizzerà di nuovo la vecchia posizione.\n" -"\n" -"Cosa vuoi fare adesso?" +msgid "Starting with %1% 2.3, configuration directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\nThis directory did not exist yet (maybe you run the new version for the first time).\nHowever, an old %1% configuration directory was detected in \n%3%.\n\nConsider moving the contents of the old directory to the new location in order to access your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old location again.\n\nWhat do you want to do now?" +msgstr "A partire da %1% 2.3, la directory di configurazione su Linux è cambiata (secondo la XDG Base Directory Specification) in \n%2%.\n\nQuesta directory non esiste ancora (forse stai usando la nuova versione per la prima volta).\nTuttavia, è stata rilevata una vecchia directory di configurazione %1% in \n%3%.\n\nPrendere in considerazione la possibilità di spostare il contenuto della vecchia directory nella nuova posizione per accedere ai propri profili, ecc.\nNotare che se si decide di fare un downgrade di %1% in futuro, si utilizzerà di nuovo la vecchia posizione.\n\nCosa vuoi fare adesso?" #: src/slic3r/GUI/PrintHostDialogs.cpp:149 msgid "Status" @@ -8488,7 +8623,7 @@ msgid "Stealth mode" msgstr "Modalità silenziosa" #: src/slic3r/GUI/Plater.cpp:5118 -#, c-format +#, possible-c-format msgid "STL file exported to %s" msgstr "File STL esportato in %s" @@ -8501,7 +8636,7 @@ msgid "Success!" msgstr "Successo!" #: src/slic3r/GUI/Plater.cpp:2047 -#, c-format +#, possible-c-format msgid "Successfully unmounted. The device %s(%s) can now be safely removed from the computer." msgstr "Smontato correttamente. Il dispositivo %s(%s) può ora essere rimosso dal computer in sicurezza." @@ -8629,12 +8764,12 @@ msgid "Supports stealth mode" msgstr "Supporto modalità silenziosa" #: src/slic3r/GUI/ConfigManipulation.cpp:158 -msgid "" -"Supports work better, if the following feature is enabled:\n" -"- Detect bridging perimeters" -msgstr "" -"I supporti funzionano meglio se le la seguente funzione è attivata:\n" -"- Rileva perimetri ponte" +msgid "Supports work better, if the following feature is enabled:\n- Detect bridging perimeters" +msgstr "I supporti funzionano meglio se le la seguente funzione è attivata:\n- Rileva perimetri ponte" + +#: src/slic3r/GUI/DoubleSlider.cpp:1824 +msgid "Supprese show the ruler" +msgstr "Nascondi il righello" #: src/slic3r/GUI/Preferences.cpp:104 msgid "Suppress \" - default - \" presets" @@ -8644,6 +8779,10 @@ msgstr "Nascondi i preset \" - default - \"" msgid "Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available." msgstr "Nascondi i preset \" - default - \" nelle selezioni Stampa / Filamento / Stampante non appena sono disponibili altri preset validi." +#: src/slic3r/GUI/Preferences.cpp:276 +msgid "Suppress to open hyperlink in browser" +msgstr "Elimina per aprire il collegamento nel browser" + #: src/slic3r/GUI/MainFrame.cpp:1527 msgid "SVG" msgstr "SVG" @@ -8681,7 +8820,7 @@ msgid "Switch to Settings" msgstr "Passa a Impostazioni" #: src/slic3r/GUI/wxExtensions.cpp:623 -#, c-format +#, possible-c-format msgid "Switch to the %s mode" msgstr "Passa alla modalità %s" @@ -8690,22 +8829,12 @@ msgid "Switching Presets: Unsaved Changes" msgstr "Cambio di preset: Modifiche non salvate" #: src/slic3r/GUI/GUI_App.cpp:1608 -msgid "" -"Switching the language will trigger application restart.\n" -"You will lose content of the plater." -msgstr "" -"Il cambio della lingua necessita il riavvio dell'applicazione.\n" -"Verrà cancellato il contenuto del piano." +msgid "Switching the language will trigger application restart.\nYou will lose content of the plater." +msgstr "Il cambio della lingua necessita il riavvio dell'applicazione.\nVerrà cancellato il contenuto del piano." #: src/slic3r/GUI/WipeTowerDialog.cpp:365 -msgid "" -"Switching to simple settings will discard changes done in the advanced mode!\n" -"\n" -"Do you want to proceed?" -msgstr "" -"Cambiare alle impostazioni semplici eliminerà tutte le modifiche fatte alle impostazioni complesse!\n" -"\n" -"Procedere?" +msgid "Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?" +msgstr "Cambiare alle impostazioni semplici eliminerà tutte le modifiche fatte alle impostazioni complesse!\n\nProcedere?" #: src/slic3r/GUI/Tab.cpp:1332 msgid "symbolic profile name" @@ -8777,31 +8906,22 @@ msgid "The %1% infill pattern is not supposed to work at 100%% density." msgstr "La trama di riempimento %1% non è fatta per lavorare con densità al 100%%." #: src/slic3r/GUI/FirmwareDialog.cpp:548 -#, c-format +#, possible-c-format msgid "The %s device could not have been found" msgstr "Il dispositivo %s non è stato trovato" #: src/slic3r/GUI/FirmwareDialog.cpp:436 -#, c-format -msgid "" -"The %s device was not found.\n" -"If the device is connected, please press the Reset button next to the USB connector ..." -msgstr "" -"Il dispositivo %s non è stato trovato.\n" -"Se il dispositivo è connesso, premi il pulsante Reset vicino al connettore USB ..." +#, possible-c-format +msgid "The %s device was not found.\nIf the device is connected, please press the Reset button next to the USB connector ..." +msgstr "Il dispositivo %s non è stato trovato.\nSe il dispositivo è connesso, premi il pulsante Reset vicino al connettore USB ..." #: src/slic3r/GUI/Tab.cpp:1238 msgid "The current custom preset will be detached from the parent system preset." msgstr "Il preset personalizzato corrente sarà staccato dal preset del sistema padre." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:925 -msgid "" -"The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" -"Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n" -"once the rotation is embedded into the object coordinates." -msgstr "" -"L'oggetto modificato corrente è inclinato (angoli di rotazione non multipli di 90°).\n" -"Un ridimensionamento non uniforme di un oggetto inclinato è possibile solamente su un sistema di coordinate reali, non appena la rotazione è inclusa nelle coordinate dell'oggetto." +msgid "The currently manipulated object is tilted (rotation angles are not multiples of 90°).\nNon-uniform scaling of tilted objects is only possible in the World coordinate system,\nonce the rotation is embedded into the object coordinates." +msgstr "L'oggetto modificato corrente è inclinato (angoli di rotazione non multipli di 90°).\nUn ridimensionamento non uniforme di un oggetto inclinato è possibile solamente su un sistema di coordinate reali, non appena la rotazione è inclusa nelle coordinate dell'oggetto." #: src/libslic3r/PrintConfig.cpp:2890 msgid "The default angle for connecting support sticks and junctions." @@ -8934,14 +9054,8 @@ msgid "The percentage of smaller pillars compared to the normal pillar diameter msgstr "La percentuale di pilastri più piccoli rispetto al diametro normale dei pilastri che vengono utilizzati in aree problematiche in cui un normale pilastro non può adattarsi." #: src/libslic3r/PrintConfig.cpp:2567 -msgid "" -"The percentage of the bed area. \n" -"If the print area exceeds the specified value, \n" -"then a slow tilt will be used, otherwise - a fast tilt" -msgstr "" -"La percentuale dell'area del piano.\n" -"Se l'area di stampa supera un determinato valore,\n" -"verrà utilizzata l'inclinazione lenta, in caso contrario - l'inclinazione veloce" +msgid "The percentage of the bed area. \nIf the print area exceeds the specified value, \nthen a slow tilt will be used, otherwise - a fast tilt" +msgstr "La percentuale dell'area del piano.\nSe l'area di stampa supera un determinato valore,\nverrà utilizzata l'inclinazione lenta, in caso contrario - l'inclinazione veloce" #: src/slic3r/GUI/Tab.cpp:3430 msgid "The physical printer(s) below is based on the preset, you are going to delete." @@ -8988,22 +9102,12 @@ msgid "The selected object couldn't be split because it contains only one part." msgstr "L'oggetto selezionato non può essere diviso perché contiene solo una parte." #: src/slic3r/GUI/MainFrame.cpp:1003 -msgid "" -"The selected project is no longer available.\n" -"Do you want to remove it from the recent projects list?" -msgstr "" -"Il progetto selezionato non è più disponibile.\n" -"Vuoi rimuoverlo dall'elenco dei progetti recenti?" +msgid "The selected project is no longer available.\nDo you want to remove it from the recent projects list?" +msgstr "Il progetto selezionato non è più disponibile.\nVuoi rimuoverlo dall'elenco dei progetti recenti?" #: src/slic3r/GUI/DoubleSlider.cpp:1121 -msgid "" -"The sequential print is on.\n" -"It's impossible to apply any custom G-code for objects printing sequentually.\n" -"This code won't be processed during G-code generation." -msgstr "" -"La stampa sequenziale è attiva.\n" -"Non è possibile applicare alcun G-code personalizzato per oggetti con stampa sequenziale.\n" -"Questo codice non sarà processato durante la generazione del G-code." +msgid "The sequential print is on.\nIt's impossible to apply any custom G-code for objects printing sequentually.\nThis code won't be processed during G-code generation." +msgstr "La stampa sequenziale è attiva.\nNon è possibile applicare alcun G-code personalizzato per oggetti con stampa sequenziale.\nQuesto codice non sarà processato durante la generazione del G-code." #: src/slic3r/GUI/ConfigWizard.cpp:1187 msgid "The size of the object can be specified in inches" @@ -9022,23 +9126,9 @@ msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "Velocità delle retrazioni (si applica solamente al motore dell'estrusore)." #: src/slic3r/GUI/ConfigManipulation.cpp:80 -#, c-format -msgid "" -"The Spiral Vase mode requires:\n" -"- one perimeter\n" -"- no top solid layers\n" -"- 0% fill density\n" -"- no support material\n" -"- Ensure vertical shell thickness enabled\n" -"- Detect thin walls disabled" -msgstr "" -"La modalità Vaso a spirale necessita:\n" -"-un solo perimetro\n" -"-nessun layer solido superiore\n" -"-densità riempimento 0%\n" -"-nessun materiale di supporto\n" -"-Mantieni spessore guscio verticale attivo\n" -"-Rileva perimetri sottili disattivo" +#, possible-c-format +msgid "The Spiral Vase mode requires:\n- one perimeter\n- no top solid layers\n- 0% fill density\n- no support material\n- Ensure vertical shell thickness enabled\n- Detect thin walls disabled" +msgstr "La modalità Vaso a spirale necessita:\n-un solo perimetro\n-nessun layer solido superiore\n-densità riempimento 0%\n-nessun materiale di supporto\n-Mantieni spessore guscio verticale attivo\n-Rileva perimetri sottili disattivo" #: src/libslic3r/Print.cpp:1263 msgid "The Spiral Vase option can only be used when printing a single object." @@ -9076,24 +9166,15 @@ msgid "The vertical distance between object and support material interface. Sett msgstr "Distanza verticale tra oggetto e interfaccia del materiale di supporto. Impostando questo valore a 0 eviterà che Slic3r utilizzi il flusso e velocità bridge per il primo layer dell'oggetto." #: src/slic3r/GUI/Tab.cpp:2731 -msgid "" -"The Wipe option is not available when using the Firmware Retraction mode.\n" -"\n" -"Shall I disable it in order to enable Firmware Retraction?" -msgstr "" -"La funzione Wipe non è disponibile quando si usa la modalità Retrazione Firmware.\n" -"\n" -"Devo disattivarla per poter abilitare la Retrazione Firmware?" +msgid "The Wipe option is not available when using the Firmware Retraction mode.\n\nShall I disable it in order to enable Firmware Retraction?" +msgstr "La funzione Wipe non è disponibile quando si usa la modalità Retrazione Firmware.\n\nDevo disattivarla per poter abilitare la Retrazione Firmware?" #: src/libslic3r/Print.cpp:1294 msgid "The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)." msgstr "La Torre di Spurgo attualmente non supporta la volumetrica E (use_volumetric_e=0)." #: src/slic3r/GUI/ConfigManipulation.cpp:114 -msgid "" -"The Wipe Tower currently supports the non-soluble supports only\n" -"if they are printed with the current extruder without triggering a tool change.\n" -"(both support_material_extruder and support_material_interface_extruder need to be set to 0)." +msgid "The Wipe Tower currently supports the non-soluble supports only\nif they are printed with the current extruder without triggering a tool change.\n(both support_material_extruder and support_material_interface_extruder need to be set to 0)." msgstr "La Torre di Spurgo attualmente è compatibile con i supporti non solubili solamente se questi sono stampati con l'attuale estrusore senza l'innesco di un cambio attrezzo. (entrambi support_material_extruder e support_material_interface_extruder devono essere impostati a 0)." #: src/libslic3r/Print.cpp:1426 @@ -9145,45 +9226,29 @@ msgid "There are unprintable objects. Try to adjust support settings to make the msgstr "Sono presenti oggetti non stampabili. Prova a regolare le impostazioni dei supporti per rendere gli oggetti stampabili." #: src/slic3r/GUI/DoubleSlider.cpp:1155 -msgid "" -"There is a color change for extruder that has not been used before.\n" -"Check your settings to avoid redundant color changes." -msgstr "" -"È presente un cambio colore per l'estrusore che non è stato usato prima.\n" -"Controlla le impostazioni per evitare cambi colore ridondanti." +msgid "There is a color change for extruder that has not been used before.\nCheck your settings to avoid redundant color changes." +msgstr "È presente un cambio colore per l'estrusore che non è stato usato prima.\nControlla le impostazioni per evitare cambi colore ridondanti." #: src/slic3r/GUI/DoubleSlider.cpp:1149 -msgid "" -"There is a color change for extruder that won't be used till the end of print job.\n" -"This code won't be processed during G-code generation." -msgstr "" -"È presente un cambio colore per l'estrusore che non sarà utilizzato fino alla fine del lavoro di stampa.\n" -"Questo codice non sarà processato durante la generazione del G-code." +msgid "There is a color change for extruder that won't be used till the end of print job.\nThis code won't be processed during G-code generation." +msgstr "È presente un cambio colore per l'estrusore che non sarà utilizzato fino alla fine del lavoro di stampa.\nQuesto codice non sarà processato durante la generazione del G-code." #: src/slic3r/GUI/DoubleSlider.cpp:1152 -msgid "" -"There is an extruder change set to the same extruder.\n" -"This code won't be processed during G-code generation." -msgstr "" -"È presente un cambio estrusore impostato nello stesso estrusore.\n" -"Questo codice non verrà processato durante la generazione del G-code." +msgid "There is an extruder change set to the same extruder.\nThis code won't be processed during G-code generation." +msgstr "È presente un cambio estrusore impostato nello stesso estrusore.\nQuesto codice non verrà processato durante la generazione del G-code." #: src/libslic3r/GCode.cpp:604 msgid "There is an object with no extrusions on the first layer." msgstr "C'è un oggetto senza estrusioni sul primo layer." #: src/slic3r/GUI/UpdateDialogs.cpp:225 -#, c-format +#, possible-c-format msgid "This %s version: %s" msgstr "%s versione: %s" #: src/slic3r/GUI/Tab.cpp:1244 -msgid "" -"This action is not revertable.\n" -"Do you want to proceed?" -msgstr "" -"Questa azione non è reversibile.\n" -"Vuoi continuare?" +msgid "This action is not revertable.\nDo you want to proceed?" +msgstr "Questa azione non è reversibile.\nVuoi continuare?" #: src/libslic3r/PrintConfig.cpp:199 msgid "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want." @@ -9258,30 +9323,13 @@ msgid "This file cannot be loaded in a simple mode. Do you want to switch to an msgstr "Non è possibile caricare questo file in modalità semplice. Si desidera passare alla modalità avanzata?" #: src/slic3r/GUI/Plater.cpp:2319 -msgid "" -"This file contains several objects positioned at multiple heights.\n" -"Instead of considering them as multiple objects, should I consider\n" -"this file as a single object having multiple parts?" -msgstr "" -"Questo file contiene numerosi oggetti posizionati ad altezze multiple. Invece di considerarli come oggetti multipli, devo considerare \n" -"questo file come un oggetto singolo con parti multiple?" +msgid "This file contains several objects positioned at multiple heights.\nInstead of considering them as multiple objects, should I consider\nthis file as a single object having multiple parts?" +msgstr "Questo file contiene numerosi oggetti posizionati ad altezze multiple. Invece di considerarli come oggetti multipli, devo considerare \nquesto file come un oggetto singolo con parti multiple?" #: src/slic3r/GUI/FirmwareDialog.cpp:332 -#, c-format -msgid "" -"This firmware hex file does not match the printer model.\n" -"The hex file is intended for: %s\n" -"Printer reported: %s\n" -"\n" -"Do you want to continue and flash this hex file anyway?\n" -"Please only continue if you are sure this is the right thing to do." -msgstr "" -"Questo file hex di firmware non è corretto per il modello della stampante. \n" -"Il file hex è per: %s\n" -"La stampante è: %s\n" -"\n" -"Vuoi continuare ed installare il firmware comunque?\n" -"Continua solo se sei certo che sia la cosa giusta da fare." +#, possible-c-format +msgid "This firmware hex file does not match the printer model.\nThe hex file is intended for: %s\nPrinter reported: %s\n\nDo you want to continue and flash this hex file anyway?\nPlease only continue if you are sure this is the right thing to do." +msgstr "Questo file hex di firmware non è corretto per il modello della stampante. \nIl file hex è per: %s\nLa stampante è: %s\n\nVuoi continuare ed installare il firmware comunque?\nContinua solo se sei certo che sia la cosa giusta da fare." #: src/libslic3r/PrintConfig.cpp:348 msgid "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time." @@ -9351,12 +9399,16 @@ msgstr "Questa è l'accelerazione che la stampante utilizzerà per il riempiment msgid "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters." msgstr "Questa è l'accelerazione che la stampante utilizzerà per i perimetri. Un valore alto come 9000 solitamente produce dei buoni risultati se l'hardware è all'altezza. Imposta a zero per disattivare il controllo dell'accelerazione per i perimetri." +#: src/libslic3r/PrintConfig.cpp:1582 +msgid "This is the acceleration your printer will use for perimeters. Set zero to disable acceleration control for perimeters." +msgstr "Questa è l'accelerazione che la stampante utilizzerà per i perimetri. Impostare zero per disabilitare il controllo dell'accelerazione per i perimetri." + #: src/libslic3r/PrintConfig.cpp:1435 msgid "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "Questo è il diametro dell'ugello dell'estrusore (per esempio: 0.5, 0.35 ecc.)" #: src/libslic3r/PrintConfig.cpp:1335 -#, c-format +#, possible-c-format msgid "This is the highest printable layer height for this extruder, used to cap the variable layer height and support layer height. Maximum recommended layer height is 75% of the extrusion width to achieve reasonable inter-layer adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." msgstr "Questa è la massima altezza layer stampabile per questo estrusore, usata come limite per l'altezza variabile dei layer e l'altezza dei layer di supporto. L'altezza layer massima raccomandata è il 75% della larghezza di estrusione, in modo da ottenere una buona adesione tra i layer. Se impostato a 0, l'altezza layer è limitata al 75% del diametro dell'ugello." @@ -9373,12 +9425,8 @@ msgid "This matrix describes volumes (in cubic milimetres) required to purge the msgstr "Questa matrice descrive il volume (in millimetri cubici) necessario per spurgare il filamento nella torre di spurgo per una qualunque coppia di attrezzi." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:928 -msgid "" -"This operation is irreversible.\n" -"Do you want to proceed?" -msgstr "" -"Questa operazione è irreversibile.\n" -"Vuoi continuare?" +msgid "This operation is irreversible.\nDo you want to proceed?" +msgstr "Questa operazione è irreversibile.\nVuoi continuare?" #: src/libslic3r/PrintConfig.cpp:1550 msgid "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled." @@ -9445,17 +9493,9 @@ msgid "This vector saves required volumes to change from/to each tool used on th msgstr "Questo vettore salva il volume necessario per cambiare da/a ogni attrezzo usato per la torre di spurgo. Questi valori vengono usati per semplificare la creazione dei volumi di spurgo completi." #: src/slic3r/GUI/UpdateDialogs.cpp:216 -#, c-format -msgid "" -"This version of %s is not compatible with currently installed configuration bundles.\n" -"This probably happened as a result of running an older %s after using a newer one.\n" -"\n" -"You may either exit %s and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s." -msgstr "" -"Questa versione di %s non è compatibile con gli attuali gruppi di configurazioni installati.\n" -"Probabilmente è causato dall'esecuzione di una vecchia versione di %s dopo averne utilizzata una più recente.\n" -"\n" -"Prova a chiudere %s e riprovare con una versione più recente, o prova ad effettuare nuovamente la configurazione iniziale. Così facendo creerai un'istantanea di backup della configurazione esistente prima di istallare i file compatibili con questo %s." +#, possible-c-format +msgid "This version of %s is not compatible with currently installed configuration bundles.\nThis probably happened as a result of running an older %s after using a newer one.\n\nYou may either exit %s and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s." +msgstr "Questa versione di %s non è compatibile con gli attuali gruppi di configurazioni installati.\nProbabilmente è causato dall'esecuzione di una vecchia versione di %s dopo averne utilizzata una più recente.\n\nProva a chiudere %s e riprovare con una versione più recente, o prova ad effettuare nuovamente la configurazione iniziale. Così facendo creerai un'istantanea di backup della configurazione esistente prima di istallare i file compatibili con questo %s." #: src/libslic3r/PrintConfig.cpp:2601 msgid "This will apply a gamma correction to the rasterized 2D polygons. A gamma value of zero means thresholding with the threshold in the middle. This behaviour eliminates antialiasing without losing holes in polygons." @@ -9531,10 +9571,14 @@ msgid "To use a custom CA file, please import your CA file into Certificate Stor msgstr "Per utilizzare un file CA personalizzato, importa il tuo file CA sul Certificate Store / Keychain." #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:271 -#, c-format +#, possible-c-format msgid "Toggle %c axis mirroring" msgstr "Attiva / disattiva il mirroring dell'asse %c" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:215 +msgid "Toggle vertical slider one layer mode ON/OFF" +msgstr "Attivare/disattivare il cursore verticale a un layer" + #: src/libslic3r/miniz_extension.cpp:93 msgid "too many files" msgstr "troppi file" @@ -9615,6 +9659,10 @@ msgstr "Layer solidi superiori" msgid "Top View" msgstr "Vista superiore" +#: src/libslic3r/PrintConfig.cpp:1211 +msgid "Topmost surface only" +msgstr "Solo superficie superiore più alta" + #: src/slic3r/GUI/WipeTowerDialog.cpp:285 msgid "Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded." msgstr "Il volume totale di spurgo viene calcolato sommando i due valori sotto, a seconda di quali attrezzi vengono scaricati/caricati." @@ -9675,13 +9723,9 @@ msgid "Type:" msgstr "Tipo:" #: src/slic3r/GUI/OpenGLManager.cpp:275 -#, c-format -msgid "" -"Unable to load the following shaders:\n" -"%s" -msgstr "" -"Impossibile caricare i seguenti shader:\n" -"%s" +#, possible-c-format +msgid "Unable to load the following shaders:\n%s" +msgstr "Impossibile caricare i seguenti shader:\n%s" #: src/slic3r/GUI/Plater.cpp:3233 msgid "Unable to reload:" @@ -9703,7 +9747,7 @@ msgid "Undo" msgstr "Annulla" #: src/slic3r/GUI/GLCanvas3D.cpp:4382 -#, c-format +#, possible-c-format msgid "Undo %1$d Action" msgid_plural "Undo %1$d Actions" msgstr[0] "Annulla %1$d Azione" @@ -9749,17 +9793,11 @@ msgid "UNLOCKED LOCK" msgstr "LUCCHETTO APERTO" #: src/slic3r/GUI/Tab.cpp:3719 -msgid "" -"UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\n" -"Click to reset all settings for current option group to the system (or default) values." -msgstr "" -"L'icona del LUCCHETTO APERTO indica che alcune impostazioni sono state modificate e non sono uguali ai valori di sistema (o predefinite) per il gruppo di opzioni corrente.\n" -"Clicca qui per reimpostare tutte le impostazioni del gruppo corrente ai valori di sistema (o predefiniti)." +msgid "UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system (or default) values for the current option group.\nClick to reset all settings for current option group to the system (or default) values." +msgstr "L'icona del LUCCHETTO APERTO indica che alcune impostazioni sono state modificate e non sono uguali ai valori di sistema (o predefinite) per il gruppo di opzioni corrente.\nClicca qui per reimpostare tutte le impostazioni del gruppo corrente ai valori di sistema (o predefiniti)." #: src/slic3r/GUI/Tab.cpp:3734 -msgid "" -"UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system (or default) value.\n" -"Click to reset current value to the system (or default) value." +msgid "UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system (or default) value.\nClick to reset current value to the system (or default) value." msgstr "L'icona del LUCCHETTO APERTO indica che il valore è stato cambiato e non è uguale al valore di sistema (o predefinito). Clicca per reimpostare il valore corrente al valore di sistema (o predefinito)." #: src/slic3r/GUI/KBShortcutsDialog.cpp:173 @@ -9799,7 +9837,7 @@ msgid "up to" msgstr "fino a" #: src/slic3r/GUI/GLCanvas3D.cpp:961 -#, c-format +#, possible-c-format msgid "up to %.2f mm" msgstr "fino a %.2f mm" @@ -9844,10 +9882,18 @@ msgstr "Caricamento" msgid "Upper Layer" msgstr "Layer superiore" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:218 +msgid "Upper layer" +msgstr "Layer superiore" + #: src/slic3r/GUI/DoubleSlider.cpp:1276 msgid "Use another extruder" msgstr "Usa un altro estrusore" +#: src/slic3r/GUI/GLCanvas3D.cpp:3959 +msgid "Use CTRL+left mouse key to enter text edit mode:" +msgstr "Usare CTRL+tasto sinistro del mouse per entrare nella modalità di modifica del testo:" + #: src/slic3r/GUI/Preferences.cpp:220 msgid "Use custom size for toolbar icons" msgstr "Utilizza dimensione personalizzata per le icone degli strumenti" @@ -10019,6 +10065,50 @@ msgstr "versione" msgid "Vertical shells" msgstr "Gusci verticali" +#: src/slic3r/GUI/KBShortcutsDialog.cpp:234 +msgid "Vertical slider - Add color change marker for current layer" +msgstr "Cursore scorrimento verticale: Aggiungi un segnale di cambio colore per il layer corrente" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:235 +msgid "Vertical slider - Delete color change marker for current layer" +msgstr "Cursore scorrimento verticale: Elimina il segnale di cambio colore per il layer corrente" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:208 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:212 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:231 +msgid "Vertical slider - Move active thumb Down" +msgstr "Cursore di scorrimento verticale - Abbassa cursore attivo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:207 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:211 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 +msgid "Vertical slider - Move active thumb Up" +msgstr "Cursore di scorrimento verticale - Solleva cursore attivo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:231 +msgid "Vertical slider - Move current thumb Down" +msgstr "Cursore di scorrimento verticale: sposta il cursore corrente verso il basso" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 +msgid "Vertical slider - Move current thumb Up" +msgstr "Cursore di scorrimento verticale: sposta il cursore corrente verso l'alto" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:233 +msgid "Vertical slider - Set lower thumb as active" +msgstr "Cursore di scorrimento verticale - Imposta cursore inferiore come attivo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:233 +msgid "Vertical slider - Set lower thumb to current thumb" +msgstr "Cursore di scorrimento verticale: sposta il cursore inferiore al cursore corrente" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:232 +msgid "Vertical slider - Set upper thumb as active" +msgstr "Cursore di scorrimento verticale - Imposta cursore superiore come attivo" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:232 +msgid "Vertical slider - Set upper thumb to current thumb" +msgstr "Cursore di scorrimento verticale: sposta il cursore superiore al cursore corrente" + #: src/slic3r/GUI/GUI_Preview.cpp:265 src/slic3r/GUI/GUI_Preview.cpp:271 msgid "View" msgstr "Vista" @@ -10028,12 +10118,8 @@ msgid "View mode" msgstr "Modalità Visualizzazione" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:666 -msgid "" -"Visit \"Preferences\" and check \"%1%\"\n" -"to be asked about unsaved changes again." -msgstr "" -"Visita \"Prefereze\" e controlla \"%1%\"\n" -"per ricevere nuovamente informazioni sui cambiamenti non salvati." +msgid "Visit \"Preferences\" and check \"%1%\"\nto be asked about unsaved changes again." +msgstr "Visita \"Prefereze\" e controlla \"%1%\"\nper ricevere nuovamente informazioni sui cambiamenti non salvati." #: src/libslic3r/PrintConfig.cpp:3553 msgid "Visualize an already sliced and saved G-code" @@ -10098,12 +10184,12 @@ msgid "Welcome" msgstr "Benvenuto" #: src/slic3r/GUI/ConfigWizard.cpp:445 -#, c-format +#, possible-c-format msgid "Welcome to the %s Configuration Assistant" msgstr "Benvenuto nell'Assistente di Configurazione di %s" #: src/slic3r/GUI/ConfigWizard.cpp:447 -#, c-format +#, possible-c-format msgid "Welcome to the %s Configuration Wizard" msgstr "Benvenuto nella Configurazione Guidata di %s" @@ -10115,6 +10201,10 @@ msgstr "Cosa desideri fare con il preset \"%1%\" dopo il salvataggio?" msgid "When checked, the print and filament presets are shown in the preset editor even if they are marked as incompatible with the active printer" msgstr "Quando attivato, i preset di stampa e di filamento vengono mostrati nell'editor dei preset anche se sono segnati come incompatibili con la stampante attiva" +#: src/slic3r/GUI/Preferences.cpp:122 +msgid "When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load." +msgstr "Quando selezionato, ogni volta che si trascina e si rilascia un file progetto sull'applicazione, viene mostrata una finestra di dialogo che chiede quale azione eseguire sul file da caricare." + #: src/slic3r/GUI/Preferences.cpp:156 msgid "When closing the application, always ask for unsaved changes" msgstr "Alla chiusura dell'applicazione, chiedere sempre riguardo le modifiche non salvate" @@ -10211,6 +10301,11 @@ msgstr "rimarrà spenta." msgid "Will inflate or deflate the sliced 2D polygons according to the sign of the correction." msgstr "Gonfierà o sgonfierà i poligoni 2D processati in base al segno della correzione." +#: src/slic3r/GUI/GCodeViewer.cpp:2660 src/slic3r/GUI/GCodeViewer.cpp:2663 +#: src/slic3r/GUI/GUI_Preview.cpp:978 +msgid "Wipe" +msgstr "Spurgo" + #: src/libslic3r/PrintConfig.cpp:2404 msgid "Wipe into this object" msgstr "Spurgo in questo oggetto" @@ -10277,18 +10372,8 @@ msgid "World coordinates" msgstr "Coordinate reali" #: src/slic3r/GUI/UpdateDialogs.cpp:92 -msgid "" -"Would you like to install it?\n" -"\n" -"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" -"\n" -"Updated configuration bundles:" -msgstr "" -"Vuoi installarlo?\n" -"\n" -"Nota: verrà prima creata un'istantanea della configurazione completa. Potrà essere ripristinata in qualunque momento se dovessero presentarsi problemi con la nuova versione.\n" -"\n" -"Gruppo di configurazioni aggiornate:" +msgid "Would you like to install it?\n\nNote that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n\nUpdated configuration bundles:" +msgstr "Vuoi installarlo?\n\nNota: verrà prima creata un'istantanea della configurazione completa. Potrà essere ripristinata in qualunque momento se dovessero presentarsi problemi con la nuova versione.\n\nGruppo di configurazioni aggiornate:" #: src/libslic3r/miniz_extension.cpp:151 msgid "write calledback failed" @@ -10359,7 +10444,7 @@ msgid "You can't change a type of the last solid part of the object." msgstr "Non è possibile modificare il tipo dell'ultima parte solida dell'oggetto." #: src/slic3r/GUI/Plater.cpp:2352 -#, c-format +#, possible-c-format msgid "You can't to add the object(s) from %s because of one or some of them is(are) multi-part" msgstr "Non è possibile aggiungere oggetti da %s perché uno o più sono multi-parte" @@ -10372,12 +10457,8 @@ msgid "You cannot use non-uniform scaling mode for multiple objects/parts select msgstr "Non è possibile utilizzare la modalità di ridimensionamento non uniforme per una selezione di più oggetti/parti" #: src/slic3r/GUI/SavePresetDialog.cpp:277 -msgid "" -"You have selected physical printer \"%1%\" \n" -"with related printer preset \"%2%\"" -msgstr "" -"Hai selezionato la stampante fisica \"%1%\" \n" -"con il relativo preset stampante \"%2%\"" +msgid "You have selected physical printer \"%1%\" \nwith related printer preset \"%2%\"" +msgstr "Hai selezionato la stampante fisica \"%1%\" \ncon il relativo preset stampante \"%2%\"" #: src/slic3r/GUI/GUI_App.cpp:1078 msgid "You have the following presets with saved options for \"Print Host upload\"" @@ -10392,7 +10473,7 @@ msgid "You must install a configuration update." msgstr "È necessario installare un aggiornamento della configurazione." #: src/slic3r/GUI/Preferences.cpp:299 -#, c-format +#, possible-c-format msgid "You need to restart %s to make the changes effective." msgstr "È necessario riavviare %s per rendere effettive le modifiche." @@ -10401,7 +10482,7 @@ msgid "You should to change a name of your printer device. It can't be saved." msgstr "Devi cambiare il nome del dispositivo di stampa. Non può essere salvato." #: src/slic3r/GUI/GUI_ObjectList.cpp:3884 -#, c-format +#, possible-c-format msgid "You started your selection with %s Item." msgstr "Hai iniziato la selezione con %s elementi." @@ -10434,24 +10515,12 @@ msgid "Z offset" msgstr "Offset Z" #: src/slic3r/GUI/ConfigManipulation.cpp:59 -msgid "" -"Zero first layer height is not valid.\n" -"\n" -"The first layer height will be reset to 0.01." -msgstr "" -"Altezza primo layer a zero non è valida.\n" -"\n" -"L'altezza del primo layer verrà reimpostata a 0.01." +msgid "Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01." +msgstr "Altezza primo layer a zero non è valida.\n\nL'altezza del primo layer verrà reimpostata a 0.01." #: src/slic3r/GUI/ConfigManipulation.cpp:47 -msgid "" -"Zero layer height is not valid.\n" -"\n" -"The layer height will be reset to 0.01." -msgstr "" -"Altezza layer zero non valida.\n" -"\n" -"L'altezza layer verrà reimpostata a 0.01." +msgid "Zero layer height is not valid.\n\nThe layer height will be reset to 0.01." +msgstr "Altezza layer zero non valida.\n\nL'altezza layer verrà reimpostata a 0.01." #: src/libslic3r/PrintConfig.cpp:2831 msgid "Zig-Zag" @@ -10475,12 +10544,8 @@ msgid "Zoom to Bed" msgstr "Zoom sul piano" #: src/slic3r/GUI/KBShortcutsDialog.cpp:176 -msgid "" -"Zoom to selected object\n" -"or all objects in scene, if none selected" -msgstr "" -"Zoom sull'oggetto selezionato\n" -"o tutti gli oggetti in scena, se nessuno è selezionato" +msgid "Zoom to selected object\nor all objects in scene, if none selected" +msgstr "Zoom sull'oggetto selezionato\no tutti gli oggetti in scena, se nessuno è selezionato" #: src/libslic3r/PrintConfig.cpp:241 src/libslic3r/PrintConfig.cpp:816 #: src/libslic3r/PrintConfig.cpp:1748 src/libslic3r/PrintConfig.cpp:1758 diff --git a/resources/localization/nl/PrusaSlicer.mo b/resources/localization/nl/PrusaSlicer.mo index 6c68f3922b46f630d4849c896db9c0efc5432464..79dbed8f2880fcaa12a630afa20fb4ad8f6b929d 100644 GIT binary patch delta 95687 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