diff --git a/resources/icons/sla_view_original.svg b/resources/icons/sla_view_original.svg new file mode 100644 index 000000000..4691721c8 --- /dev/null +++ b/resources/icons/sla_view_original.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + diff --git a/resources/icons/sla_view_processed.svg b/resources/icons/sla_view_processed.svg new file mode 100644 index 000000000..a26a0db2f --- /dev/null +++ b/resources/icons/sla_view_processed.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index dff8aea9f..5aed97842 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -166,6 +166,8 @@ namespace ImGui const wchar_t PauseHoverButton = 0x261B; const wchar_t OpenButton = 0x261C; const wchar_t OpenHoverButton = 0x261D; + const wchar_t SlaViewOriginal = 0x261E; + const wchar_t SlaViewProcessed = 0x261F; const wchar_t LegendTravel = 0x2701; const wchar_t LegendWipe = 0x2702; diff --git a/src/libslic3r/Algorithm/PathSorting.hpp b/src/libslic3r/Algorithm/PathSorting.hpp new file mode 100644 index 000000000..ab4462728 --- /dev/null +++ b/src/libslic3r/Algorithm/PathSorting.hpp @@ -0,0 +1,128 @@ +#ifndef SRC_LIBSLIC3R_PATH_SORTING_HPP_ +#define SRC_LIBSLIC3R_PATH_SORTING_HPP_ + +#include "AABBTreeLines.hpp" +#include "BoundingBox.hpp" +#include "Line.hpp" +#include "ankerl/unordered_dense.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { +namespace Algorithm { + +//Sorts the paths such that all paths between begin and last_seed are printed first, in some order. The rest of the paths is sorted +// such that the paths that are touching some of the already printed are printed first, sorted secondary by the distance to the last point of the last +// printed path. +// begin, end, and last_seed are random access iterators. touch_limit_distance is used to check if the paths are touching - if any part of the path gets this close +// to the second, then they touch. +// convert_to_lines is a lambda that should accept the path as argument and return it as Lines vector, in correct order. +template +void sort_paths(RandomAccessIterator begin, RandomAccessIterator end, Point start, double touch_limit_distance, ToLines convert_to_lines) +{ + size_t paths_count = std::distance(begin, end); + if (paths_count <= 1) + return; + + auto paths_touch = [touch_limit_distance](const AABBTreeLines::LinesDistancer &left, + const AABBTreeLines::LinesDistancer &right) { + for (const Line &l : left.get_lines()) { + if (right.distance_from_lines(l.a) < touch_limit_distance) { + return true; + } + } + if (right.distance_from_lines(left.get_lines().back().b) < touch_limit_distance) { + return true; + } + + for (const Line &l : right.get_lines()) { + if (left.distance_from_lines(l.a) < touch_limit_distance) { + return true; + } + } + if (left.distance_from_lines(right.get_lines().back().b) < touch_limit_distance) { + return true; + } + return false; + }; + + std::vector> distancers(paths_count); + for (size_t path_idx = 0; path_idx < paths_count; path_idx++) { + distancers[path_idx] = AABBTreeLines::LinesDistancer{convert_to_lines(*std::next(begin, path_idx))}; + } + + std::vector> dependencies(paths_count); + for (size_t path_idx = 0; path_idx < paths_count; path_idx++) { + for (size_t next_path_idx = path_idx + 1; next_path_idx < paths_count; next_path_idx++) { + if (paths_touch(distancers[path_idx], distancers[next_path_idx])) { + dependencies[next_path_idx].insert(path_idx); + } + } + } + + Point current_point = start; + + std::vector> correct_order_and_direction(paths_count); + size_t unsorted_idx = 0; + size_t null_idx = size_t(-1); + size_t next_idx = null_idx; + bool reverse = false; + while (unsorted_idx < paths_count) { + next_idx = null_idx; + double lines_dist = std::numeric_limits::max(); + for (size_t path_idx = 0; path_idx < paths_count; path_idx++) { + if (!dependencies[path_idx].empty()) + continue; + + double ldist = distancers[path_idx].distance_from_lines(current_point); + if (ldist < lines_dist) { + const auto &lines = distancers[path_idx].get_lines(); + double dist_a = (lines.front().a - current_point).cast().squaredNorm(); + double dist_b = (lines.back().b - current_point).cast().squaredNorm(); + next_idx = path_idx; + reverse = dist_b < dist_a; + lines_dist = ldist; + } + } + + // we have valid next_idx, sort it, update dependencies, update current point + correct_order_and_direction[next_idx] = {unsorted_idx, reverse}; + unsorted_idx++; + current_point = reverse ? distancers[next_idx].get_lines().front().a : distancers[next_idx].get_lines().back().b; + + dependencies[next_idx].insert(null_idx); // prevent it from being selected again + for (size_t path_idx = 0; path_idx < paths_count; path_idx++) { + dependencies[path_idx].erase(next_idx); + } + } + + for (size_t path_idx = 0; path_idx < paths_count; path_idx++) { + if (correct_order_and_direction[path_idx].second) { + std::next(begin, path_idx)->reverse(); + } + } + + for (size_t i = 0; i < correct_order_and_direction.size() - 1; i++) { + bool swapped = false; + for (size_t j = 0; j < correct_order_and_direction.size() - i - 1; j++) { + if (correct_order_and_direction[j].first > correct_order_and_direction[j + 1].first) { + std::swap(correct_order_and_direction[j], correct_order_and_direction[j + 1]); + std::iter_swap(std::next(begin, j), std::next(begin, j + 1)); + swapped = true; + } + } + if (swapped == false) { + break; + } + } +} + +}} // namespace Slic3r::Algorithm + +#endif /*SRC_LIBSLIC3R_PATH_SORTING_HPP_*/ \ No newline at end of file diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 297d2e3ff..b94f94d9b 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -22,8 +22,9 @@ set(SLIC3R_SOURCES AABBTreeLines.hpp AABBMesh.hpp AABBMesh.cpp - Algorithm/RegionExpansion.cpp + Algorithm/PathSorting.hpp Algorithm/RegionExpansion.hpp + Algorithm/RegionExpansion.cpp AnyPtr.hpp BoundingBox.cpp BoundingBox.hpp diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index a6e5e1fb4..abf4b1a2b 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -306,47 +306,15 @@ std::vector group_fills(const Layer &layer) } } - // Detect narrow internal solid infill area and use ipEnsuring pattern instead. - { - std::vector narrow_expolygons; - static constexpr const auto narrow_pattern = ipEnsuring; - for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id) - if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) { - size_t num_expolygons = fill.expolygons.size(); - narrow_expolygons.clear(); - narrow_expolygons.reserve(num_expolygons); - // Detect narrow expolygons. - int num_narrow = 0; - for (const ExPolygon &ex : fill.expolygons) { - bool narrow = offset_ex(ex, -scaled(NarrowInfillAreaThresholdMM)).empty(); - num_narrow += int(narrow); - narrow_expolygons.emplace_back(narrow); - } - if (num_narrow == num_expolygons) { - // All expolygons are narrow, change the fill pattern. - fill.params.pattern = narrow_pattern; - } else if (num_narrow > 0) { - // Some expolygons are narrow, split the fills. - params = fill.params; - params.pattern = narrow_pattern; - surface_fills.emplace_back(params); - SurfaceFill &old_fill = surface_fills[surface_fill_id]; - SurfaceFill &new_fill = surface_fills.back(); - new_fill.region_id = old_fill.region_id; - new_fill.surface.surface_type = stInternalSolid; - new_fill.surface.thickness = old_fill.surface.thickness; - new_fill.expolygons.reserve(num_narrow); - for (size_t i = 0; i < narrow_expolygons.size(); ++ i) - if (narrow_expolygons[i]) - new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i])); - old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(), - [&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }), - old_fill.expolygons.end()); - } - } - } + // Use ipEnsuring pattern for all internal Solids. + { + for (size_t surface_fill_id = 0; surface_fill_id < surface_fills.size(); ++surface_fill_id) + if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) { + fill.params.pattern = ipEnsuring; + } + } - return surface_fills; + return surface_fills; } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index cf3766758..4130cc331 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -147,9 +147,9 @@ protected: virtual float _layer_angle(size_t idx) const { return (idx & 1) ? float(M_PI/2.) : 0; } - virtual std::pair _infill_direction(const Surface *surface) const; public: + virtual std::pair _infill_direction(const Surface *surface) const; static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams ¶ms); static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); static void connect_infill(Polylines &&infill_ordered, const std::vector &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms); diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp index 2522aee93..21c4dac3d 100644 --- a/src/libslic3r/Fill/FillEnsuring.cpp +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -2,81 +2,470 @@ #include "../ShortestPath.hpp" #include "../Arachne/WallToolPaths.hpp" +#include "AABBTreeLines.hpp" +#include "Algorithm/PathSorting.hpp" +#include "BoundingBox.hpp" +#include "ExPolygon.hpp" #include "FillEnsuring.hpp" +#include "KDTreeIndirect.hpp" +#include "Line.hpp" +#include "Point.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" +#include "SVG.hpp" +#include "libslic3r.h" +#include #include +#include +#include +#include +#include +#include namespace Slic3r { -ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const FillParams ¶ms) +ThickPolylines make_fill_polylines( + const Fill *fill, const Surface *surface, const FillParams ¶ms, bool stop_vibrations, bool fill_gaps, bool connect_extrusions) { - assert(params.use_arachne); - assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); + assert(fill->print_config != nullptr && fill->print_object_config != nullptr && fill->print_region_config != nullptr); - const coord_t scaled_spacing = scaled(this->spacing); - - // Perform offset. - Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; - // Create the infills for each of the regions. - ThickPolylines thick_polylines_out; - for (ExPolygon &ex_poly : expp) { - Point bbox_size = ex_poly.contour.bounding_box().size(); - coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1; - Polygons polygons = to_polygons(ex_poly); - Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config); - if (std::vector loops = wall_tool_paths.getToolPaths(); !loops.empty()) { - std::vector all_extrusions; - for (Arachne::VariableWidthLines &loop : loops) { - if (loop.empty()) - continue; - for (const Arachne::ExtrusionLine &wall : loop) - all_extrusions.emplace_back(&wall); + auto rotate_thick_polylines = [](ThickPolylines &tpolylines, double cos_angle, double sin_angle) { + for (ThickPolyline &tp : tpolylines) { + for (auto &p : tp.points) { + double px = double(p.x()); + double py = double(p.y()); + p.x() = coord_t(round(cos_angle * px - sin_angle * py)); + p.y() = coord_t(round(cos_angle * py + sin_angle * px)); } + } + }; - // Split paths using a nearest neighbor search. - size_t firts_poly_idx = thick_polylines_out.size(); - Point last_pos(0, 0); - for (const Arachne::ExtrusionLine *extrusion : all_extrusions) { - if (extrusion->empty()) - continue; + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) || + (bhigh >= alow && bhigh <= ahigh); + }; - ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); - if (thick_polyline.length() == 0.) - //FIXME this should not happen. - continue; - assert(thick_polyline.size() > 1); - assert(thick_polyline.length() > 0.); - //assert(thick_polyline.points.size() == thick_polyline.width.size()); - if (extrusion->is_closed) - thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); + const coord_t scaled_spacing = scaled(fill->spacing); + double distance_limit_reconnection = 2.0 * double(scaled_spacing); + double squared_distance_limit_reconnection = distance_limit_reconnection * distance_limit_reconnection; + Polygons filled_area = to_polygons(surface->expolygon); + std::pair rotate_vector = fill->_infill_direction(surface); + double aligning_angle = -rotate_vector.first + PI; + polygons_rotate(filled_area, aligning_angle); + BoundingBox bb = get_extents(filled_area); - assert(thick_polyline.size() > 1); - //assert(thick_polyline.points.size() == thick_polyline.width.size()); - thick_polylines_out.emplace_back(std::move(thick_polyline)); - last_pos = thick_polylines_out.back().last_point(); - } + Polygons inner_area = stop_vibrations ? intersection(filled_area, opening(filled_area, 2 * scaled_spacing, 3 * scaled_spacing)) : + filled_area; + + inner_area = shrink(inner_area, scaled_spacing * 0.5 - scaled(fill->overlap)); + + AABBTreeLines::LinesDistancer area_walls{to_lines(inner_area)}; - // clip the paths to prevent the extruder from getting exactly on the first point of the loop - // Keep valid paths only. - size_t j = firts_poly_idx; - for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { - assert(thick_polylines_out[i].size() > 1); - assert(thick_polylines_out[i].length() > 0.); - //assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size()); - thick_polylines_out[i].clip_end(this->loop_clipping); - assert(thick_polylines_out[i].size() > 1); - if (thick_polylines_out[i].is_valid()) { - if (j < i) - thick_polylines_out[j] = std::move(thick_polylines_out[i]); - ++j; + const size_t n_vlines = (bb.max.x() - bb.min.x() + scaled_spacing - 1) / scaled_spacing; + std::vector vertical_lines(n_vlines); + coord_t y_min = bb.min.y(); + coord_t y_max = bb.max.y(); + for (size_t i = 0; i < n_vlines; i++) { + coord_t x = bb.min.x() + i * double(scaled_spacing); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; + } + vertical_lines.push_back(vertical_lines.back()); + vertical_lines.back().a = Point{coord_t(bb.min.x() + n_vlines * double(scaled_spacing) + scaled_spacing * 0.5), y_min}; + vertical_lines.back().b = Point{vertical_lines.back().a.x(), y_max}; + + std::vector> polygon_sections(n_vlines); + + for (size_t i = 0; i < n_vlines; i++) { + const auto intersections = area_walls.intersections_with_line(vertical_lines[i]); + + for (int intersection_idx = 0; intersection_idx < int(intersections.size()) - 1; intersection_idx++) { + const auto &a = intersections[intersection_idx]; + const auto &b = intersections[intersection_idx + 1]; + if (area_walls.outside((a.first + b.first) / 2) < 0) { + if (std::abs(a.first.y() - b.first.y()) > scaled_spacing) { + polygon_sections[i].emplace_back(a.first, b.first); } } - if (j < thick_polylines_out.size()) - thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); } } - return thick_polylines_out; + if (stop_vibrations) { + struct Node + { + int section_idx; + int line_idx; + int skips_taken = 0; + bool neighbours_explored = false; + std::vector> neighbours{}; + }; + + coord_t length_filter = scale_(4); + size_t skips_allowed = 2; + size_t min_removal_conut = 5; + for (int section_idx = 0; section_idx < polygon_sections.size(); section_idx++) { + for (int line_idx = 0; line_idx < polygon_sections[section_idx].size(); line_idx++) { + if (const Line &line = polygon_sections[section_idx][line_idx]; line.a != line.b && line.length() < length_filter) { + std::set> to_remove{{section_idx, line_idx}}; + std::vector to_visit{{section_idx, line_idx}}; + + bool initial_touches_long_lines = false; + if (section_idx > 0) { + for (int prev_line_idx = 0; prev_line_idx < polygon_sections[section_idx - 1].size(); prev_line_idx++) { + if (const Line &nl = polygon_sections[section_idx - 1][prev_line_idx]; + nl.a != nl.b && segments_overlap(line.a.y(), line.b.y(), nl.a.y(), nl.b.y())) { + initial_touches_long_lines = true; + } + } + } + + while (!to_visit.empty()) { + Node curr = to_visit.back(); + const Line &curr_l = polygon_sections[curr.section_idx][curr.line_idx]; + if (curr.neighbours_explored) { + bool is_valid_for_removal = (curr_l.length() < length_filter) && + ((int(to_remove.size()) - curr.skips_taken > min_removal_conut) || + (curr.neighbours.empty() && !initial_touches_long_lines)); + if (!is_valid_for_removal) { + for (const auto &n : curr.neighbours) { + if (to_remove.find(n) != to_remove.end()) { + is_valid_for_removal = true; + break; + } + } + } + if (!is_valid_for_removal) { + to_remove.erase({curr.section_idx, curr.line_idx}); + } + to_visit.pop_back(); + } else { + to_visit.back().neighbours_explored = true; + int curr_index = to_visit.size() - 1; + bool can_use_skip = curr_l.length() <= length_filter && curr.skips_taken < skips_allowed; + if (curr.section_idx + 1 < polygon_sections.size()) { + for (int lidx = 0; lidx < polygon_sections[curr.section_idx + 1].size(); lidx++) { + if (const Line &nl = polygon_sections[curr.section_idx + 1][lidx]; + nl.a != nl.b && segments_overlap(curr_l.a.y(), curr_l.b.y(), nl.a.y(), nl.b.y()) && + (nl.length() < length_filter || can_use_skip)) { + to_visit[curr_index].neighbours.push_back({curr.section_idx + 1, lidx}); + to_remove.insert({curr.section_idx + 1, lidx}); + Node next_node{curr.section_idx + 1, lidx, curr.skips_taken + (nl.length() >= length_filter)}; + to_visit.push_back(next_node); + } + } + } + } + } + + for (const auto &pair : to_remove) { + Line &l = polygon_sections[pair.first][pair.second]; + l.a = l.b; + } + } + } + } + } + + for (size_t section_idx = 0; section_idx < polygon_sections.size(); section_idx++) { + polygon_sections[section_idx].erase(std::remove_if(polygon_sections[section_idx].begin(), polygon_sections[section_idx].end(), + [](const Line &s) { return s.a == s.b; }), + polygon_sections[section_idx].end()); + std::sort(polygon_sections[section_idx].begin(), polygon_sections[section_idx].end(), + [](const Line &a, const Line &b) { return a.a.y() < b.b.y(); }); + } + + ThickPolylines thick_polylines; + { + for (const auto &polygon_slice : polygon_sections) { + for (const Line &segment : polygon_slice) { + ThickPolyline &new_path = thick_polylines.emplace_back(); + new_path.points.push_back(segment.a); + new_path.width.push_back(scaled_spacing); + new_path.points.push_back(segment.b); + new_path.width.push_back(scaled_spacing); + new_path.endpoints = {true, true}; + } + } + } + + if (fill_gaps) { + Polygons reconstructed_area{}; + // reconstruct polygon from polygon sections + { + struct TracedPoly + { + Points lows; + Points highs; + }; + + std::vector> polygon_sections_w_width = polygon_sections; + for (auto &slice : polygon_sections_w_width) { + for (Line &l : slice) { + l.a -= Point{0.0, 0.5 * scaled_spacing}; + l.b += Point{0.0, 0.5 * scaled_spacing}; + } + } + + std::vector current_traced_polys; + for (const auto &polygon_slice : polygon_sections_w_width) { + std::unordered_set used_segments; + for (TracedPoly &traced_poly : current_traced_polys) { + auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(), + [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); + auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(), + [](const Point &high, const Line &seg) { return seg.a.y() > high.y(); }); + + bool segment_added = false; + for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) { + if (used_segments.find(&(*candidate)) != used_segments.end()) { + continue; + } + if (connect_extrusions && (traced_poly.lows.back() - candidates_begin->a).cast().squaredNorm() < + squared_distance_limit_reconnection) { + traced_poly.lows.push_back(candidates_begin->a); + } else { + traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0}); + traced_poly.lows.push_back(candidates_begin->a - Point{scaled_spacing / 2, 0}); + traced_poly.lows.push_back(candidates_begin->a); + } + + if (connect_extrusions && (traced_poly.highs.back() - candidates_begin->b).cast().squaredNorm() < + squared_distance_limit_reconnection) { + traced_poly.highs.push_back(candidates_begin->b); + } else { + traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0}); + traced_poly.highs.push_back(candidates_begin->b - Point{scaled_spacing / 2, 0}); + traced_poly.highs.push_back(candidates_begin->b); + } + segment_added = true; + used_segments.insert(&(*candidates_begin)); + } + + if (!segment_added) { + // Zero or multiple overlapping segments. Resolving this is nontrivial, + // so we just close this polygon and maybe open several new. This will hopefully happen much less often + traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0}); + Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + traced_poly.lows.clear(); + traced_poly.highs.clear(); + } + } + + current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }), + current_traced_polys.end()); + + for (const auto &segment : polygon_slice) { + if (used_segments.find(&segment) == used_segments.end()) { + TracedPoly &new_tp = current_traced_polys.emplace_back(); + new_tp.lows.push_back(segment.a - Point{scaled_spacing / 2, 0}); + new_tp.lows.push_back(segment.a); + new_tp.highs.push_back(segment.b - Point{scaled_spacing / 2, 0}); + new_tp.highs.push_back(segment.b); + } + } + } + + // add not closed polys + for (TracedPoly &traced_poly : current_traced_polys) { + Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + } + } + + reconstructed_area = closing(reconstructed_area, float(SCALED_EPSILON), float(SCALED_EPSILON)); + ExPolygons gaps_for_additional_filling = diff_ex(filled_area, reconstructed_area); + if (fill->overlap != 0) { + gaps_for_additional_filling = offset_ex(gaps_for_additional_filling, scaled(fill->overlap)); + } + + // BoundingBox bbox = get_extents(filled_area); + // bbox.offset(scale_(1.)); + // ::Slic3r::SVG svg(debug_out_path(("surface" + std::to_string(surface->area())).c_str()).c_str(), bbox); + // svg.draw(to_lines(filled_area), "red", scale_(0.4)); + // svg.draw(to_lines(reconstructed_area), "blue", scale_(0.3)); + // svg.draw(to_lines(gaps_for_additional_filling), "green", scale_(0.2)); + // svg.draw(vertical_lines, "black", scale_(0.1)); + // svg.Close(); + + for (ExPolygon &ex_poly : gaps_for_additional_filling) { + BoundingBox ex_bb = ex_poly.contour.bounding_box(); + coord_t loops_count = (std::max(ex_bb.size().x(), ex_bb.size().y()) + scaled_spacing - 1) / scaled_spacing; + Polygons polygons = to_polygons(ex_poly); + Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, + *fill->print_object_config, *fill->print_config); + if (std::vector loops = wall_tool_paths.getToolPaths(); !loops.empty()) { + std::vector all_extrusions; + for (Arachne::VariableWidthLines &loop : loops) { + if (loop.empty()) + continue; + for (const Arachne::ExtrusionLine &wall : loop) + all_extrusions.emplace_back(&wall); + } + + for (const Arachne::ExtrusionLine *extrusion : all_extrusions) { + if (extrusion->junctions.size() < 2) { + continue; + } + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); + if (extrusion->is_closed) { + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, ex_bb.min)); + thick_polyline.clip_end(scaled_spacing * 0.5); + } + if (thick_polyline.is_valid() && thick_polyline.length() > 0 && thick_polyline.points.size() > 1) { + thick_polylines.push_back(thick_polyline); + } + } + } + } + + std::sort(thick_polylines.begin(), thick_polylines.end(), [](const ThickPolyline &left, const ThickPolyline &right) { + BoundingBox lbb(left.points); + BoundingBox rbb(right.points); + if (lbb.min.x() == rbb.min.x()) + return lbb.min.y() < rbb.min.y(); + else + return lbb.min.x() < rbb.min.x(); + }); + + // connect tiny gap fills to close colinear line + struct EndPoint + { + Vec2d position; + size_t polyline_idx; + size_t other_end_point_idx; + bool is_first; + bool used = false; + }; + std::vector connection_endpoints; + connection_endpoints.reserve(thick_polylines.size() * 2); + for (size_t pl_idx = 0; pl_idx < thick_polylines.size(); pl_idx++) { + size_t current_idx = connection_endpoints.size(); + connection_endpoints.push_back({thick_polylines[pl_idx].first_point().cast(), pl_idx, current_idx + 1, true}); + connection_endpoints.push_back({thick_polylines[pl_idx].last_point().cast(), pl_idx, current_idx, false}); + } + + std::vector linear_segment_flags(thick_polylines.size()); + for (size_t i = 0;i < thick_polylines.size(); i++) { + const ThickPolyline& tp = thick_polylines[i]; + linear_segment_flags[i] = tp.points.size() == 2 && tp.points.front().x() == tp.points.back().x() && + tp.width.front() == scaled_spacing && tp.width.back() == scaled_spacing; + } + + auto coord_fn = [&connection_endpoints](size_t idx, size_t dim) { return connection_endpoints[idx].position[dim]; }; + KDTreeIndirect<2, double, decltype(coord_fn)> endpoints_tree{coord_fn, connection_endpoints.size()}; + for (size_t ep_idx = 0; ep_idx < connection_endpoints.size(); ep_idx++) { + EndPoint &ep1 = connection_endpoints[ep_idx]; + if (!ep1.used) { + std::vector close_endpoints = find_nearby_points(endpoints_tree, ep1.position, double(scaled_spacing)); + for (size_t close_endpoint_idx : close_endpoints) { + EndPoint &ep2 = connection_endpoints[close_endpoint_idx]; + if (ep2.used || ep2.polyline_idx == ep1.polyline_idx || + (linear_segment_flags[ep1.polyline_idx] && linear_segment_flags[ep2.polyline_idx])) { + continue; + } + + EndPoint &target_ep = ep1.polyline_idx > ep2.polyline_idx ? ep1 : ep2; + EndPoint &source_ep = ep1.polyline_idx > ep2.polyline_idx ? ep2 : ep1; + + ThickPolyline &target_tp = thick_polylines[target_ep.polyline_idx]; + ThickPolyline &source_tp = thick_polylines[source_ep.polyline_idx]; + linear_segment_flags[target_ep.polyline_idx] = linear_segment_flags[ep1.polyline_idx] || + linear_segment_flags[ep2.polyline_idx]; + + Vec2d v1 = target_ep.is_first ? + (target_tp.points[0] - target_tp.points[1]).cast() : + (target_tp.points.back() - target_tp.points[target_tp.points.size() - 1]).cast(); + Vec2d v2 = source_ep.is_first ? + (source_tp.points[1] - source_tp.points[0]).cast() : + (source_tp.points[source_tp.points.size() - 1] - source_tp.points.back()).cast(); + + if (std::abs(Slic3r::angle(v1, v2)) > PI / 6.0) { + continue; + } + + // connect target_ep and source_ep, result is stored in target_tp, source_tp will be cleared + if (target_ep.is_first) { + target_tp.reverse(); + target_ep.is_first = false; + connection_endpoints[target_ep.other_end_point_idx].is_first = true; + } + + size_t new_start_idx = target_ep.other_end_point_idx; + + if (!source_ep.is_first) { + source_tp.reverse(); + source_ep.is_first = true; + connection_endpoints[source_ep.other_end_point_idx].is_first = false; + } + + size_t new_end_idx = source_ep.other_end_point_idx; + + target_tp.points.insert(target_tp.points.end(), source_tp.points.begin(), source_tp.points.end()); + target_tp.width.push_back(target_tp.width.back()); + target_tp.width.push_back(source_tp.width.front()); + target_tp.width.insert(target_tp.width.end(), source_tp.width.begin(), source_tp.width.end()); + target_ep.used = true; + source_ep.used = true; + + connection_endpoints[new_start_idx].polyline_idx = target_ep.polyline_idx; + connection_endpoints[new_end_idx].polyline_idx = target_ep.polyline_idx; + connection_endpoints[new_start_idx].other_end_point_idx = new_end_idx; + connection_endpoints[new_end_idx].other_end_point_idx = new_start_idx; + source_tp.clear(); + break; + } + } + } + + thick_polylines.erase(std::remove_if(thick_polylines.begin(), thick_polylines.end(), + [scaled_spacing](const ThickPolyline &tp) { + return tp.length() < scaled_spacing && + std::all_of(tp.width.begin(), tp.width.end(), + [scaled_spacing](double w) { return w < scaled_spacing; }); + }), + thick_polylines.end()); + } + + Algorithm::sort_paths(thick_polylines.begin(), thick_polylines.end(), bb.min, double(scaled_spacing) * 1.2, [](const ThickPolyline &tp) { + Lines ls; + Point prev = tp.first_point(); + for (size_t i = 1; i < tp.points.size(); i++) { + ls.emplace_back(prev, tp.points[i]); + prev = ls.back().b; + } + return ls; + }); + + if (connect_extrusions) { + ThickPolylines connected_thick_polylines; + if (!thick_polylines.empty()) { + connected_thick_polylines.push_back(thick_polylines.front()); + for (size_t tp_idx = 1; tp_idx < thick_polylines.size(); tp_idx++) { + ThickPolyline &tp = thick_polylines[tp_idx]; + ThickPolyline &tail = connected_thick_polylines.back(); + Point last = tail.last_point(); + if ((last - tp.last_point()).cast().squaredNorm() < (last - tp.first_point()).cast().squaredNorm()) { + tp.reverse(); + } + if ((last - tp.first_point()).cast().squaredNorm() < squared_distance_limit_reconnection) { + tail.points.insert(tail.points.end(), tp.points.begin(), tp.points.end()); + tail.width.push_back(scaled_spacing); + tail.width.push_back(scaled_spacing); + tail.width.insert(tail.width.end(), tp.width.begin(), tp.width.end()); + } else { + connected_thick_polylines.push_back(tp); + } + } + } + thick_polylines = connected_thick_polylines; + } + + rotate_thick_polylines(thick_polylines, cos(-aligning_angle), sin(-aligning_angle)); + return thick_polylines; } } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillEnsuring.hpp b/src/libslic3r/Fill/FillEnsuring.hpp index faa080153..c20c93aff 100644 --- a/src/libslic3r/Fill/FillEnsuring.hpp +++ b/src/libslic3r/Fill/FillEnsuring.hpp @@ -6,13 +6,19 @@ namespace Slic3r { -class FillEnsuring : public FillRectilinear +ThickPolylines make_fill_polylines( + const Fill *fill, const Surface *surface, const FillParams ¶ms, bool stop_vibrations, bool fill_gaps, bool connect_extrusions); + +class FillEnsuring : public Fill { public: Fill *clone() const override { return new FillEnsuring(*this); } ~FillEnsuring() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override { return {}; }; - ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override; + ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override + { + return make_fill_polylines(this, surface, params, true, true, true); + }; protected: void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out); diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index e9fc058e8..6b7f57b7f 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -438,7 +438,7 @@ ConfigSubstitutions SL1Reader::read(std::vector &slices, ConfigSubstitutions SL1Reader::read(DynamicPrintConfig &out) { - ZipperArchive arch = read_zipper_archive(m_fname, {}, {"png"}); + ZipperArchive arch = read_zipper_archive(m_fname, {"ini"}, {"png", "thumbnail"}); return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable); } diff --git a/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp b/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp index 5c40a5c51..17d3fa9a0 100644 --- a/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp +++ b/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp @@ -5,6 +5,7 @@ #include "SL1.hpp" #include "SL1_SVG.hpp" #include "AnycubicSLA.hpp" +#include "I18N.hpp" #include "SLAArchiveFormatRegistry.hpp" diff --git a/src/libslic3r/Format/ZipperArchiveImport.cpp b/src/libslic3r/Format/ZipperArchiveImport.cpp index 2bd5f555b..657e420bb 100644 --- a/src/libslic3r/Format/ZipperArchiveImport.cpp +++ b/src/libslic3r/Format/ZipperArchiveImport.cpp @@ -83,8 +83,15 @@ ZipperArchive read_zipper_archive(const std::string &zipfname, })) continue; - if (name == CONFIG_FNAME) { arch.config = read_ini(entry, zip); continue; } - if (name == PROFILE_FNAME) { arch.profile = read_ini(entry, zip); continue; } + if (name == CONFIG_FNAME) { + arch.config = read_ini(entry, zip); + continue; + } + + if (name == PROFILE_FNAME) { + arch.profile = read_ini(entry, zip); + continue; + } auto it = std::lower_bound( arch.entries.begin(), arch.entries.end(), diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 9dab740e5..8b5f64662 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -5,6 +5,7 @@ #include "libslic3r/format.hpp" #include "libslic3r/I18N.hpp" #include "libslic3r/GCodeWriter.hpp" +#include "libslic3r/I18N.hpp" #include "GCodeProcessor.hpp" #include diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 837f32479..f2a1db3c9 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2097,6 +2097,14 @@ bool ModelObject::has_solid_mesh() const return false; } +bool ModelObject::has_negative_volume_mesh() const +{ + for (const ModelVolume* volume : volumes) + if (volume->is_negative_volume()) + return true; + return false; +} + void ModelVolume::set_material_id(t_model_material_id material_id) { m_material_id = material_id; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index ea22b968d..359e1fbbd 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -503,6 +503,8 @@ public: // Detect if object has at least one solid mash bool has_solid_mesh() const; + // Detect if object has at least one negative volume mash + bool has_negative_volume_mesh() const; bool is_cut() const { return cut_id.id().valid(); } bool has_connectors() const; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index e1068d763..b6c543bd9 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -728,7 +729,7 @@ Polylines reconnect_polylines(const Polylines &polylines, double limit_distance) return result; } -ExtrusionPaths sort_extra_perimeters(ExtrusionPaths extra_perims, int index_of_first_unanchored, double extrusion_spacing) +ExtrusionPaths sort_extra_perimeters(const ExtrusionPaths& extra_perims, int index_of_first_unanchored, double extrusion_spacing) { if (extra_perims.empty()) return {}; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 8766c6d86..703e50cfa 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -206,6 +206,9 @@ struct ThickPolyline { void start_at_index(int index); Points points; + // vector of startpoint width and endpoint width of each line segment. The size should be always (points.size()-1) * 2 + // e.g. let four be points a,b,c,d. that are three lines ab, bc, cd. for each line, there should be start width, so the width vector is: + // w(a), w(b), w(b), w(c), w(c), w(d) std::vector width; std::pair endpoints { false, false }; }; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d20514bba..b63576a0d 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -403,7 +403,9 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) is_visible = app_config.get_variant(vendor->id, model, variant); } else if (type == TYPE_FILAMENT || type == TYPE_SLA_MATERIAL) { const std::string §ion_name = (type == TYPE_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; - if (app_config.has_section(section_name)) { + if (type == TYPE_FILAMENT && app_config.get_bool("no_templates") && vendor && vendor->templates_profile) + is_visible = false; + else if (app_config.has_section(section_name)) { // Check whether this profile is marked as "installed" in PrusaSlicer.ini, // or whether a profile is marked as "installed", which this profile may have been renamed from. const std::map &installed = app_config.get_section(section_name); @@ -896,8 +898,9 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string return preset; } -void PresetCollection::save_current_preset(const std::string &new_name, bool detach) +bool PresetCollection::save_current_preset(const std::string &new_name, bool detach) { + bool is_saved_as_new{ false }; // 1) Find the preset with a new_name or create a new one, // initialize it with the edited config. auto it = this->find_preset_internal(new_name); @@ -906,7 +909,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det Preset &preset = *it; if (preset.is_default || preset.is_external || preset.is_system) // Cannot overwrite the default preset. - return; + return false; // Overwriting an existing preset. preset.config = std::move(m_edited_preset.config); // The newly saved preset will be activated -> make it visible. @@ -919,6 +922,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det preset.renamed_from.clear(); } } else { + is_saved_as_new = true; // Creating a new preset. Preset &preset = *m_presets.insert(it, m_edited_preset); std::string &inherits = preset.inherits(); @@ -953,6 +957,8 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det this->select_preset_by_name(new_name, true); // 2) Store the active preset to disk. this->get_selected_preset().save(); + + return is_saved_as_new; } Preset& PresetCollection::get_preset_with_name(const std::string& new_name, const Preset* initial_preset) @@ -1212,7 +1218,13 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil if (selected) preset_selected.is_compatible = preset_edited.is_compatible; if (preset_edited.vendor && preset_edited.vendor->templates_profile) { - indices_of_template_presets.push_back(idx_preset); + if (preset_selected.is_visible) + indices_of_template_presets.push_back(idx_preset); + else { + preset_selected.is_compatible = false; + if (selected) + m_idx_selected = size_t(-1); + } } } // filter out template profiles where profile with same alias and compability exists @@ -2092,6 +2104,136 @@ bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIter m_selected_preset == preset_name; } +ExtruderFilaments::ExtruderFilaments(PresetCollection* filaments_collection, size_t extruder_id, std::string selected_name/* = std::string()*/) +: m_filaments (filaments_collection) +, m_extruder_id(extruder_id) +{ + const std::deque& presets = m_filaments->get_presets(); + for (size_t id = 0; id < presets.size(); id ++) + m_extr_filaments.emplace_back(&(presets[id])); + + select_filament(selected_name.empty() ? m_filaments->get_selected_preset_name() : selected_name); +} + +const std::string& ExtruderFilaments::get_preset_name_by_alias(const std::string& alias) const +{ + const auto& aliases_map = m_filaments->map_alias_to_profile_name(); + for ( + // Find the 1st profile name with the alias. + auto it = Slic3r::lower_bound_by_predicate(aliases_map.begin(), aliases_map.end(), [&alias](auto& l) { return l.first < alias; }); + // Continue over all profile names with the same alias. + it != aliases_map.end() && it->first == alias; ++it) + if (auto it_filament = find_filament_internal(it->second); + it_filament != m_extr_filaments.end() && it_filament->preset->name == it->second && + it_filament->preset->is_visible && (it_filament->is_compatible || size_t(it_filament - m_extr_filaments.begin()) == m_idx_selected)) + return it_filament->preset->name; + return alias; +} + +bool ExtruderFilaments::select_filament(const std::string &name_w_suffix, bool force/*= false*/) +{ + std::string name = Preset::remove_suffix_modified(name_w_suffix); + // 1) Try to find the preset by its name. + auto it = this->find_filament_internal(name); + size_t idx = 0; + if (it != m_extr_filaments.end() && it->preset->name == name && it->preset->is_visible) + // Preset found by its name and it is visible. + idx = it - m_extr_filaments.begin(); + else { + // Find the first visible preset. + for (size_t i = 0; i < m_extr_filaments.size(); ++i) + if (m_extr_filaments[i].preset->is_visible/* && m_extr_filaments[i].is_compatible*/) { + idx = i; + break; + } + // If the first visible preset was not found, return the 0th element, which is the default preset. + } + // 2) Select the new preset. + if (m_idx_selected != idx || force) { + this->select_filament(idx); + return true; + } + + return false; +} + +size_t ExtruderFilaments::update_compatible_internal(const PresetWithVendorProfile &active_printer, + const PresetWithVendorProfile *active_print, + PresetSelectCompatibleType unselect_if_incompatible) +{ + DynamicPrintConfig config; + config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); + const ConfigOption* opt = active_printer.preset.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + bool some_compatible = false; + + // Adjust printer preset config to the first extruder from m_extruder_id + Preset printer_preset_adjusted = active_printer.preset; + if (m_extruder_id > 0 && !printer_preset_adjusted.config.opt_bool("single_extruder_multi_material")) { + DynamicPrintConfig& active_printer_config = printer_preset_adjusted.config; + for (const std::string& key : print_config_def.extruder_option_keys()) { + if (key == "default_filament_profile") + continue;// Ignore this field, because this parameter is not related to the extruder but to whole printer. + auto* opt = active_printer_config.option(key, false); + if (opt != nullptr && opt->is_vector()) + static_cast(opt)->set_at(opt, 0, m_extruder_id); + } + } + PresetWithVendorProfile active_printer_adjusted(printer_preset_adjusted, active_printer.vendor); + + std::vector indices_of_template_presets; + indices_of_template_presets.reserve(m_extr_filaments.size()); + + size_t num_default_presets = m_filaments->num_default_presets(); + for (size_t idx_preset = num_default_presets; idx_preset < m_extr_filaments.size(); ++idx_preset) { + const bool is_selected = idx_preset == m_idx_selected; + const Preset* preset = m_extr_filaments[idx_preset].preset; + Filament& extr_filament = m_extr_filaments[idx_preset]; + + const PresetWithVendorProfile this_preset_with_vendor_profile = m_filaments->get_preset_with_vendor_profile(*preset); + bool was_compatible = extr_filament.is_compatible; + extr_filament.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer_adjusted, &config); + some_compatible |= extr_filament.is_compatible; + if (active_print != nullptr) + extr_filament.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print, active_printer_adjusted); + if (!extr_filament.is_compatible && is_selected && + (unselect_if_incompatible == PresetSelectCompatibleType::Always || (unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible && was_compatible))) + m_idx_selected = size_t(-1); + if (preset->vendor && preset->vendor->templates_profile) { + if (preset->is_visible) + indices_of_template_presets.push_back(idx_preset); + else { + extr_filament.is_compatible = false; + if (is_selected) + m_idx_selected = size_t(-1); + } + } + } + + // filter out template profiles where profile with same alias and compability exists + if (!indices_of_template_presets.empty()) { + for (size_t idx = num_default_presets; idx < m_extr_filaments.size(); ++idx) { + const Filament& filament = m_extr_filaments[idx]; + const VendorProfile* vendor = filament.preset->vendor; + if (vendor && !vendor->templates_profile && filament.is_compatible) { + const std::string& preset_alias = filament.preset->alias; + for (const auto& template_idx : indices_of_template_presets) { + if (m_extr_filaments[template_idx].preset->alias == preset_alias) { + m_extr_filaments[template_idx].is_compatible = false; + // unselect selected template filament if there is non-template alias compatible + if (template_idx == m_idx_selected && (unselect_if_incompatible != PresetSelectCompatibleType::Never)) + m_idx_selected = size_t(-1); + break; + } + } + } + } + } + + return m_idx_selected; +} + namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 9a16a16a9..a0d8018a1 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -341,7 +341,8 @@ public: // Save the preset under a new name. If the name is different from the old one, // a new preset is stored into the list of presets. // All presets are marked as not modified and the new preset is activated. - void save_current_preset(const std::string &new_name, bool detach = false); + // return true, if new preset is stored + bool save_current_preset(const std::string &new_name, bool detach = false); // Find the preset with a new_name or create a new one, // initialize it with the initial_preset config. @@ -507,7 +508,7 @@ public: // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; - size_t num_default_presets() { return m_num_default_presets; } + size_t num_default_presets() const { return m_num_default_presets; } protected: PresetCollection() = default; @@ -566,6 +567,8 @@ public: static bool is_dirty(const Preset *edited, const Preset *reference); static std::vector dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare = false); static bool is_independent_from_extruder_number_option(const std::string& opt_key); + + const std::vector>& map_alias_to_profile_name() { return m_map_alias_to_profile_name; } private: // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. Preset::Type m_type; @@ -827,6 +830,142 @@ private: }; +// --------------------------------- +// *** ExtruderFilaments *** +// --------------------------------- + +class Filament +{ +public: + Filament(const Preset* preset) : preset(preset) {} + // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. + bool operator<(const Filament& other) const { return this->preset->name < other.preset->name; } + + const Preset* preset; + bool is_compatible{ true }; +}; + +// Collections of filaments for extruder +class ExtruderFilaments +{ + PresetCollection* m_filaments{ nullptr }; + // Selected filament. + size_t m_idx_selected{ size_t(-1) }; + // List of filaments for this extruder + std::deque m_extr_filaments; + + size_t m_extruder_id; + + std::string m_cached_selected_name{ std::string() }; + +public: + ExtruderFilaments(PresetCollection* filaments_collection, size_t extruder_id = 0, std::string selected_name = std::string()); + + typedef std::deque::iterator Iterator; + typedef std::deque::const_iterator ConstIterator; + Iterator begin() { return m_extr_filaments.begin(); } + ConstIterator begin() const { return m_extr_filaments.cbegin(); } + ConstIterator cbegin() const { return m_extr_filaments.cbegin(); } + Iterator end() { return m_extr_filaments.end(); } + ConstIterator end() const { return m_extr_filaments.cend(); } + ConstIterator cend() const { return m_extr_filaments.cend(); } + + bool empty() const { return m_extr_filaments.empty(); } + + const std::deque& operator()() const { return m_extr_filaments; } + + // Return a filament by an index. If the filament is active, a temporary copy is returned. + Filament& filament(size_t idx) { return m_extr_filaments[idx]; } + const Filament& filament(size_t idx) const { return const_cast(this)->filament(idx); } + + // Select filament by the full filament name, which contains name of filament, separator and name of selected preset + // If full_name doesn't contain name of selected preset, then select first preset in the list for this filament + bool select_filament(const std::string& name, bool force = false); + void select_filament(size_t idx) { m_idx_selected = idx; } + + std::string get_selected_preset_name() const { return m_idx_selected == size_t(-1) ? std::string() : m_extr_filaments[m_idx_selected].preset->name; } + const Preset* get_selected_preset() const { return m_idx_selected == size_t(-1) ? nullptr : m_extr_filaments[m_idx_selected].preset; } + const Filament* get_selected_filament() const { return m_idx_selected == size_t(-1) ? nullptr : &m_extr_filaments[m_idx_selected]; } + size_t get_selected_idx() const { return m_idx_selected; } + + friend class PresetBundle; + + ExtruderFilaments() = default; + ExtruderFilaments& operator=(const ExtruderFilaments& other) = default; + +private: + // Find a preset position in the sorted list of presets. + // The "-- default -- " preset is always the first, so it needs + // to be handled differently. + // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. + std::deque::iterator find_filament_internal(const std::string& name) + { + return Slic3r::lower_bound_by_predicate(m_extr_filaments.begin(), m_extr_filaments.end(), [&name](const auto& l) { + return l.preset->name < name; + }); + } + std::deque::const_iterator find_filament_internal(const std::string& name) const + { + return const_cast(this)->find_filament_internal(name); + } + + void cache_selected_name() { m_cached_selected_name = get_selected_preset_name(); } + std::string get_cached_selected_name() const { return m_cached_selected_name; } + + // Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. +// If one of the prefered_alternates is compatible, select it. + template + size_t first_compatible_idx(PreferedCondition prefered_condition) const + { + size_t i = m_filaments->is_default_suppressed() ? m_filaments->num_default_presets() : 0; + size_t n = m_extr_filaments.size(); + size_t i_compatible = n; + int match_quality = -1; + for (; i < n; ++i) + // Since we use the filament selection from Wizard, it's needed to control the preset visibility too + if (m_extr_filaments[i].is_compatible && m_filaments->preset(i).is_visible) { + int this_match_quality = prefered_condition(*(m_extr_filaments[i].preset)); + if (this_match_quality > match_quality) { + if (match_quality == std::numeric_limits::max()) + // Better match will not be found. + return i; + // Store the first compatible profile with highest match quality into i_compatible. + i_compatible = i; + match_quality = this_match_quality; + } + } + return (i_compatible == n) ? + // No compatible preset found, return the default preset. + 0 : + // Compatible preset found. + i_compatible; + } + // Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. + size_t first_compatible_idx() const { return this->first_compatible_idx([](const /*Filament*/Preset&) -> int { return 0; }); } + + template + const Preset* first_compatible(PreferedCondition prefered_condition) { return m_extr_filaments[this->first_compatible_idx(prefered_condition)].preset;} + const Preset* first_compatible() { return m_extr_filaments[this->first_compatible_idx()].preset; } + + const std::string& get_preset_name_by_alias(const std::string& alias) const; + + size_t update_compatible_internal(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType unselect_if_incompatible); + + // For Print / Filament presets, disable those, which are not compatible with the printer. + template + void update_compatible(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType select_other_if_incompatible, PreferedCondition prefered_condition) + { + if (this->update_compatible_internal(active_printer, active_print, select_other_if_incompatible) == (size_t)-1) + // Find some other compatible preset, or the "-- default --" preset. + this->select_filament(this->first_compatible_idx(prefered_condition)); + } + void update_compatible(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType select_other_if_incompatible) + { + this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const /*Filament*/Preset&) -> int { return 0; }); + } +}; + + } // namespace Slic3r #endif /* slic3r_Preset_hpp_ */ diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 899d24ecc..730574af1 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -119,7 +119,7 @@ PresetBundle& PresetBundle::operator=(const PresetBundle &rhs) printers = rhs.printers; physical_printers = rhs.physical_printers; - filament_presets = rhs.filament_presets; + extruders_filaments = rhs.extruders_filaments; project_config = rhs.project_config; vendors = rhs.vendors; obsolete_presets = rhs.obsolete_presets; @@ -143,8 +143,7 @@ void PresetBundle::reset(bool delete_files) this->filaments .reset(delete_files); this->sla_materials.reset(delete_files); this->printers .reset(delete_files); - this->filament_presets.clear(); - this->filament_presets.emplace_back(this->filaments.get_selected_preset_name()); + this->extruders_filaments.clear(); this->obsolete_presets.prints.clear(); this->obsolete_presets.sla_prints.clear(); this->obsolete_presets.filaments.clear(); @@ -426,7 +425,26 @@ void PresetBundle::load_installed_printers(const AppConfig &config) preset.set_visible_from_appconfig(config); } -PresetCollection& PresetBundle::get_presets(Preset::Type type) +void PresetBundle::cache_extruder_filaments_names() +{ + for (ExtruderFilaments& extr_filaments : extruders_filaments) + extr_filaments.cache_selected_name(); +} + +void PresetBundle::reset_extruder_filaments() +{ + // save previously cached selected names + std::vector names; + for (const ExtruderFilaments& extr_filaments : extruders_filaments) + names.push_back(extr_filaments.get_cached_selected_name()); + + // Reset extruder_filaments and set names + this->extruders_filaments.clear(); + for (size_t id = 0; id < names.size(); ++id) + this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, names[id])); +} + +PresetCollection&PresetBundle::get_presets(Preset::Type type) { assert(type >= Preset::TYPE_PRINT && type <= Preset::TYPE_PRINTER); @@ -437,12 +455,15 @@ PresetCollection& PresetBundle::get_presets(Preset::Type type) } -const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) +const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias, int extruder_id /*= -1*/) { // there are not aliases for Printers profiles if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID) return alias; + if (preset_type == Preset::TYPE_FILAMENT) + return extruders_filaments[extruder_id].get_preset_name_by_alias(alias); + const PresetCollection& presets = get_presets(preset_type); return presets.get_preset_name_by_alias(alias); @@ -462,8 +483,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset:: if (type == Preset::TYPE_PRINTER) copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config); + if (type == Preset::TYPE_FILAMENT) + cache_extruder_filaments_names(); // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini - presets.save_current_preset(new_name); + if (presets.save_current_preset(new_name) && type == Preset::TYPE_FILAMENT) + reset_extruder_filaments(); // Mark the print & filament enabled if they are compatible with the currently selected preset. // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. update_compatible(PresetSelectCompatibleType::Never); @@ -602,35 +626,37 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p // Load it even if the current printer technology is SLA. // The possibly excessive filament names will be later removed with this->update_multi_material_filament_presets() // once the FFF technology gets selected. - this->filament_presets = { filaments.get_selected_preset_name() }; + this->extruders_filaments.clear(); + this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments)); for (unsigned int i = 1; i < 1000; ++ i) { char name[64]; sprintf(name, "filament_%u", i); if (! config.has("presets", name)) break; - this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); + this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, i, remove_ini_suffix(config.get("presets", name)))); } + // ! update MM filaments presets before update compatibility + this->update_multi_material_filament_presets(); // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, // as the application may have been closed with an active "external" preset, which does not // exist. this->update_compatible(PresetSelectCompatibleType::Always); - this->update_multi_material_filament_presets(); if (initial_printer != nullptr && (preferred_printer == nullptr || initial_printer == preferred_printer)) { // Don't run the following code, as we want to activate default filament / SLA material profiles when installing and selecting a new printer. // Only run this code if just a filament / SLA material was installed by Config Wizard for an active Printer. auto printer_technology = printers.get_selected_preset().printer_technology(); if (printer_technology == ptFFF && ! preferred_selection.filament.empty()) { - std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament); + const std::string& preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament, 0); if (auto it = filaments.find_preset_internal(preferred_preset_name); it != filaments.end() && it->is_visible && it->is_compatible) { filaments.select_preset_by_name_strict(preferred_preset_name); - this->filament_presets.front() = filaments.get_selected_preset_name(); + this->extruders_filaments.front().select_filament(filaments.get_selected_preset_name()); } } else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) { - std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material); + const std::string& preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material); if (auto it = sla_materials.find_preset_internal(preferred_preset_name); it != sla_materials.end() && it->is_visible && it->is_compatible) sla_materials.select_preset_by_name_strict(preferred_preset_name); @@ -648,15 +674,15 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p // Export selections (current print, current filaments, current printer) into config.ini void PresetBundle::export_selections(AppConfig &config) { - assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1); - assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front()); + assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() >= 1); + assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias); config.clear_section("presets"); config.set("presets", "print", prints.get_selected_preset_name()); - config.set("presets", "filament", filament_presets.front()); - for (unsigned i = 1; i < filament_presets.size(); ++i) { + config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name()); + for (unsigned i = 1; i < extruders_filaments.size(); ++i) { char name[64]; sprintf(name, "filament_%u", i); - config.set("presets", name, filament_presets[i]); + config.set("presets", name, extruders_filaments[i].get_selected_preset_name()); } config.set("presets", "sla_print", sla_prints.get_selected_preset_name()); @@ -711,8 +737,8 @@ DynamicPrintConfig PresetBundle::full_fff_config() const // First collect the filament configurations based on the user selection of this->filament_presets. // Here this->filaments.find_preset() and this->filaments.first_visible() return the edited copy of the preset if active. std::vector filament_configs; - for (const std::string &filament_preset_name : this->filament_presets) - filament_configs.emplace_back(&this->filaments.find_preset(filament_preset_name, true)->config); + for (const auto& extr_filaments : this->extruders_filaments) + filament_configs.emplace_back(&this->filaments.find_preset(extr_filaments.get_selected_preset_name(), true)->config); while (filament_configs.size() < num_extruders) filament_configs.emplace_back(&this->filaments.first_visible().config); for (const DynamicPrintConfig *cfg : filament_configs) { @@ -763,7 +789,10 @@ DynamicPrintConfig PresetBundle::full_fff_config() const } out.option("print_settings_id", true)->value = this->prints.get_selected_preset_name(); - out.option("filament_settings_id", true)->values = this->filament_presets; + auto& filament_settings_id = out.option("filament_settings_id", true)->values; + filament_settings_id.clear(); + for (const auto& extr_filaments : this->extruders_filaments) + filament_settings_id.emplace_back(extr_filaments.get_selected_preset_name()); out.option("printer_settings_id", true)->value = this->printers.get_selected_preset_name(); out.option("physical_printer_settings_id", true)->value = this->physical_printers.get_selected_printer_name(); @@ -981,6 +1010,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool auto old_filament_profile_names = config.option("filament_settings_id", true); old_filament_profile_names->values.resize(num_extruders, std::string()); + this->extruders_filaments.clear(); if (num_extruders <= 1) { // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. inherits = inherits_values[1]; @@ -994,8 +1024,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool loaded= &this->filaments.load_preset(this->filaments.path_from_name(name), name, config); loaded->save(); } - this->filament_presets.clear(); - this->filament_presets.emplace_back(loaded->name); + this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments)); } else { assert(is_external); // Split the filament presets, load each of them separately. @@ -1014,7 +1043,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool } } // Load the configs into this->filaments and make them active. - this->filament_presets = std::vector(configs.size()); + std::vector extr_names = std::vector(configs.size()); // To avoid incorrect selection of the first filament preset (means a value of Preset->m_idx_selected) // in a case when next added preset take a place of previosly selected preset, // we should add presets from last to first @@ -1035,8 +1064,11 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool PresetCollection::LoadAndSelect::Never : PresetCollection::LoadAndSelect::OnlyIfModified); any_modified |= modified; - this->filament_presets[i] = loaded->name; + extr_names[i] = loaded->name; } + // create extruders_filaments only when all filaments are loaded + for (size_t id = 0; id < extr_names.size(); ++id) + this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, extr_names[id])); } // 4) Load the project config values (the per extruder wipe matrix etc). @@ -1137,9 +1169,11 @@ ConfigSubstitutions PresetBundle::load_config_file_config_bundle( load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments .get_selected_preset_name(), true); load_one(this->sla_materials, tmp_bundle.sla_materials, tmp_bundle.sla_materials.get_selected_preset_name(), true); load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset_name(), true); + + this->extruders_filaments.clear(); this->update_multi_material_filament_presets(); - for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i) - this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); + for (size_t i = 1; i < std::min(tmp_bundle.extruders_filaments.size(), this->extruders_filaments.size()); ++i) + this->extruders_filaments[i].select_filament(load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.extruders_filaments[i].get_selected_preset_name(), false)); this->update_compatible(PresetSelectCompatibleType::Never); @@ -1622,9 +1656,12 @@ std::pair PresetBundle::load_configbundle( // Activate the first filament preset. if (! active_filaments.empty() && ! active_filaments.front().empty()) filaments.select_preset_by_name(active_filaments.front(), true); + + // Extruder_filaments have to be recreated with new loaded filaments + this->extruders_filaments.clear(); this->update_multi_material_filament_presets(); - for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i) - this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name; + for (size_t i = 0; i < std::min(this->extruders_filaments.size(), active_filaments.size()); ++ i) + this->extruders_filaments[i].select_filament(filaments.find_preset(active_filaments[i], true)->name); this->update_compatible(PresetSelectCompatibleType::Never); } @@ -1640,10 +1677,15 @@ void PresetBundle::update_multi_material_filament_presets() auto *nozzle_diameter = static_cast(printers.get_edited_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); // Verify validity of the current filament presets. - for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i) - this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name; - // Append the rest of filament presets. - this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); + for (size_t i = 0; i < std::min(this->extruders_filaments.size(), num_extruders); ++i) + this->extruders_filaments[i].select_filament(this->filaments.find_preset(this->extruders_filaments[i].get_selected_preset_name(), true)->name); + + if (this->extruders_filaments.size() > num_extruders) + this->extruders_filaments.resize(num_extruders); + else + // Append the rest of filament presets. + for (size_t id = extruders_filaments.size(); id < num_extruders; id++) + extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, id == 0 ? filaments.first_visible().name : extruders_filaments[id - 1].get_selected_preset_name())); // Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator): std::vector old_matrix = this->project_config.option("wiping_volumes_matrix")->values; @@ -1673,6 +1715,99 @@ void PresetBundle::update_multi_material_filament_presets() } } +void PresetBundle::update_filaments_compatible(PresetSelectCompatibleType select_other_filament_if_incompatible, int extruder_idx/* = -1*/) +{ + const Preset& printer_preset = this->printers.get_edited_preset(); + const PresetWithVendorProfile printer_preset_with_vendor_profile = this->printers.get_preset_with_vendor_profile(printer_preset); + const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile(); + const std::vector& prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; + + class PreferedFilamentsProfileMatch + { + public: + PreferedFilamentsProfileMatch(const Preset* preset, const std::vector& prefered_names, int extruder_id = 0) : + m_extruder_id(extruder_id), + m_prefered_alias(preset ? preset->alias : std::string()), + m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", extruder_id) : std::string()), + m_prefered_names(prefered_names) {} + + int operator()(const Preset& preset) const + { + // Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile. + if (preset.is_default || preset.is_external) + return 0; + if (!m_prefered_alias.empty() && m_prefered_alias == preset.alias) + // Matching an alias, always take this preset with priority. + return std::numeric_limits::max(); + int match_quality = (std::find(m_prefered_names.begin(), m_prefered_names.end(), preset.name) != m_prefered_names.end()) + 1; + if (!m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", m_extruder_id)) + match_quality *= 10; + return match_quality; + } + + private: + int m_extruder_id; + const std::string m_prefered_alias; + const std::string m_prefered_filament_type; + const std::vector& m_prefered_names; + }; + + //! ysFIXME - delete after testing + //!// First select a first compatible profile for the preset editor. + //!this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible, + //! PreferedFilamentsProfileMatch(this->filaments.get_selected_idx() == size_t(-1) ? nullptr : &this->filaments.get_edited_preset(), prefered_filament_profiles)); + + // Update compatible for extruder filaments + + auto update_filament_compatible = [this, select_other_filament_if_incompatible, printer_preset_with_vendor_profile, print_preset_with_vendor_profile, prefered_filament_profiles](int idx) + { + ExtruderFilaments& extr_filaments = extruders_filaments[idx]; + + // Remember whether the filament profiles were compatible before updating the filament compatibility. + bool filament_preset_was_compatible = false; + const Filament* filament_old = extr_filaments.get_selected_filament(); + if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never) + filament_preset_was_compatible = filament_old && filament_old->is_compatible; + + extr_filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible, + PreferedFilamentsProfileMatch(filament_old ? filament_old->preset : nullptr, prefered_filament_profiles, idx)); + + const Filament* filament = extr_filaments.get_selected_filament(); + const bool is_compatible = filament && filament->is_compatible; + + if (is_compatible || select_other_filament_if_incompatible == PresetSelectCompatibleType::Never) + return; + + // Verify validity of the current filament presets. + if (this->extruders_filaments.size() == 1) { + // The compatible profile should have been already selected for the preset editor. Just use it. + if (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible) + extr_filaments.select_filament(this->filaments.get_edited_preset().name); + } + else { + const std::string filament_name = extr_filaments.get_selected_preset_name(); + if (!filament || (!is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible))) { + // Pick a compatible profile. If there are prefered_filament_profiles, use them. + std::string compat_filament_name = extr_filaments.first_compatible(PreferedFilamentsProfileMatch(filament->preset, prefered_filament_profiles, idx))->name; + if (filament_name != compat_filament_name) + extr_filaments.select_filament(compat_filament_name); + } + } + }; + + if (extruder_idx < 0) { + // update compatibility for all extruders + const size_t num_extruders = static_cast(printer_preset.config.option("nozzle_diameter"))->values.size(); + for (size_t idx = 0; idx < std::min(this->extruders_filaments.size(), num_extruders); idx++) + update_filament_compatible(idx); + } + else + update_filament_compatible(extruder_idx); + + if (this->filaments.get_idx_selected() == size_t(-1)) + this->filaments.select_preset(extruders_filaments[0].get_selected_idx()); +} + void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible) { const Preset &printer_preset = this->printers.get_edited_preset(); @@ -1727,99 +1862,18 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri const double m_prefered_layer_height; }; - // Matching by the layer height in addition. - class PreferedFilamentProfileMatch : public PreferedProfileMatch - { - public: - PreferedFilamentProfileMatch(const Preset *preset, const std::string &prefered_name) : - PreferedProfileMatch(preset ? preset->alias : std::string(), prefered_name), - m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", 0) : std::string()) {} - - int operator()(const Preset &preset) const - { - // Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile. - if (preset.is_default || preset.is_external) - return 0; - int match_quality = PreferedProfileMatch::operator()(preset); - if (match_quality < std::numeric_limits::max()) { - match_quality += 1; - if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0)) - match_quality *= 10; - } - return match_quality; - } - - private: - const std::string m_prefered_filament_type; - }; - - // Matching by the layer height in addition. - class PreferedFilamentsProfileMatch - { - public: - PreferedFilamentsProfileMatch(const Preset *preset, const std::vector &prefered_names) : - m_prefered_alias(preset ? preset->alias : std::string()), - m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", 0) : std::string()), - m_prefered_names(prefered_names) - {} - - int operator()(const Preset &preset) const - { - // Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile. - if (preset.is_default || preset.is_external) - return 0; - if (! m_prefered_alias.empty() && m_prefered_alias == preset.alias) - // Matching an alias, always take this preset with priority. - return std::numeric_limits::max(); - int match_quality = (std::find(m_prefered_names.begin(), m_prefered_names.end(), preset.name) != m_prefered_names.end()) + 1; - if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0)) - match_quality *= 10; - return match_quality; - } - - private: - const std::string m_prefered_alias; - const std::string m_prefered_filament_type; - const std::vector &m_prefered_names; - }; - switch (printer_preset.printer_technology()) { case ptFFF: { assert(printer_preset.config.has("default_print_profile")); assert(printer_preset.config.has("default_filament_profile")); - const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; + this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible, PreferedPrintProfileMatch(this->prints.get_selected_idx() == size_t(-1) ? nullptr : &this->prints.get_edited_preset(), printer_preset.config.opt_string("default_print_profile"))); - const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile(); - // Remember whether the filament profiles were compatible before updating the filament compatibility. - std::vector filament_preset_was_compatible(this->filament_presets.size(), false); - for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) { - Preset *preset = this->filaments.find_preset(this->filament_presets[idx], false); - filament_preset_was_compatible[idx] = preset != nullptr && preset->is_compatible; - } - // First select a first compatible profile for the preset editor. - this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible, - PreferedFilamentsProfileMatch(this->filaments.get_selected_idx() == size_t(-1) ? nullptr : &this->filaments.get_edited_preset(), prefered_filament_profiles)); - if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never) { - // Verify validity of the current filament presets. - const std::string prefered_filament_profile = prefered_filament_profiles.empty() ? std::string() : prefered_filament_profiles.front(); - if (this->filament_presets.size() == 1) { - // The compatible profile should have been already selected for the preset editor. Just use it. - if (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible.front()) - this->filament_presets.front() = this->filaments.get_edited_preset().name; - } else { - for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) { - std::string &filament_name = this->filament_presets[idx]; - Preset *preset = this->filaments.find_preset(filament_name, false); - if (preset == nullptr || (! preset->is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible[idx]))) - // Pick a compatible profile. If there are prefered_filament_profiles, use them. - filament_name = this->filaments.first_compatible( - PreferedFilamentProfileMatch(preset, - (idx < prefered_filament_profiles.size()) ? prefered_filament_profiles[idx] : prefered_filament_profile)).name; - } - } - } + + // Update compatibility for all currently existent extruder_filaments. + update_filaments_compatible(select_other_filament_if_incompatible); + break; } case ptSLA: @@ -1875,13 +1929,13 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst c << "sla_print = " << this->sla_prints.get_selected_preset_name() << std::endl; c << "sla_material = " << this->sla_materials.get_selected_preset_name() << std::endl; c << "printer = " << this->printers.get_selected_preset_name() << std::endl; - for (size_t i = 0; i < this->filament_presets.size(); ++ i) { + for (size_t i = 0; i < this->extruders_filaments.size(); ++ i) { char suffix[64]; if (i > 0) sprintf(suffix, "_%d", (int)i); else suffix[0] = 0; - c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl; + c << "filament" << suffix << " = " << this->extruders_filaments[i].get_selected_preset_name() << std::endl; } if (export_physical_printers && this->physical_printers.get_selected_idx() >= 0) @@ -1901,9 +1955,11 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) { - if (idx >= filament_presets.size()) - filament_presets.resize(idx + 1, filaments.default_preset().name); - filament_presets[idx] = Preset::remove_suffix_modified(name); + if (idx >= extruders_filaments.size()) { + for (size_t id = extruders_filaments.size(); id < idx; id++) + extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, filaments.default_preset().name)); + } + extruders_filaments[idx].select_filament(Preset::remove_suffix_modified(name)); } void PresetBundle::set_default_suppressed(bool default_suppressed) diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 7d5a3a4f6..3da6699f9 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -51,9 +51,12 @@ public: const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; } PrinterPresetCollection printers; PhysicalPrinterCollection physical_printers; - // Filament preset names for a multi-extruder or multi-material print. - // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() - std::vector filament_presets; + + // Filament presets per extruder for a multi-extruder or multi-material print. + // extruders_filaments.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() + std::vector extruders_filaments; + void cache_extruder_filaments_names(); + void reset_extruder_filaments(); PresetCollection& get_presets(Preset::Type preset_type); @@ -132,6 +135,8 @@ public: // update size and content of filament_presets. void update_multi_material_filament_presets(); + void update_filaments_compatible(PresetSelectCompatibleType select_other_filament_if_incompatible, int extruder_idx = -1); + // Update the is_compatible flag of all print and filament presets depending on whether they are marked // as compatible with the currently selected printer (and print in case of filament presets). // Also updates the is_visible flag of each preset. @@ -145,7 +150,7 @@ public: // If the "vendor" section is missing, enable all models and variants of the particular vendor. void load_installed_printers(const AppConfig &config); - const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias); + const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias, int extruder_id = -1); // Save current preset of a provided type under a new name. If the name is different from the old one, // Unselected option would be reverted to the beginning values diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1c37339a2..5d3028c8f 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -21,6 +21,7 @@ #include "Support/TreeSupport.hpp" #include "Surface.hpp" #include "Slicing.hpp" +#include "SurfaceCollection.hpp" #include "Tesselate.hpp" #include "TriangleMeshSlicer.hpp" #include "Utils.hpp" @@ -36,7 +37,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -1607,7 +1610,7 @@ void PrintObject::discover_vertical_shells() #ifdef DEBUG_BRIDGE_OVER_INFILL template void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d) { - std::vector colors = {"red", "blue", "orange", "green"}; + std::vector colors = {"red", "green", "blue", "orange"}; BoundingBox bbox = get_extents(a); bbox.merge(get_extents(b)); bbox.merge(get_extents(c)); @@ -1664,11 +1667,7 @@ void PrintObject::bridge_over_infill() // unsupported area will serve as a filter for polygons worth bridging. Polygons unsupported_area; Polygons lower_layer_solids; - bool contains_only_lightning = true; for (const LayerRegion *region : layer->lower_layer->regions()) { - if (region->region().config().fill_pattern.value != ipLightning) { - contains_only_lightning = false; - } Polygons fill_polys = to_polygons(region->fill_expolygons()); // initially consider the whole layer unsupported, but also gather solid layers to later cut off supported parts unsupported_area.insert(unsupported_area.end(), fill_polys.begin(), fill_polys.end()); @@ -1729,6 +1728,98 @@ void PrintObject::bridge_over_infill() } } + // LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the + // lightning infill under them get expanded. This somewhat helps to ensure that most of the extrusions are anchored to the lightning infill at the ends. + // It requires modifying this instance of print object in a specific way, so that we do not invalidate the pointers in our surfaces_by_layer structure. + bool has_lightning_infill = false; + for (size_t i = 0; i < this->num_printing_regions(); i++) { + if (this->printing_region(i).config().fill_pattern == ipLightning) { + has_lightning_infill = true; + break; + } + } + if (has_lightning_infill) { + // Prepare backup data for the Layer Region infills. Before modfiyng the layer region, we backup its fill surfaces by moving! them into this map. + // then a copy is created, modifiyed and passed to lightning infill generator. After generator is created, we restore the original state of the fills + // again by moving the data from this map back to the layer regions. This ensures that pointers to surfaces stay valid. + std::map> backup_surfaces; + for (size_t lidx = 0; lidx < this->layer_count(); lidx++) { + backup_surfaces[lidx] = {}; + } + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &backup_surfaces, + &surfaces_by_layer](tbb::blocked_range r) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end()) + continue; + + Layer *layer = po->get_layer(lidx); + const Layer *lower_layer = layer->lower_layer; + if (lower_layer == nullptr) + continue; + + Polygons lightning_fill; + for (const LayerRegion *region : lower_layer->regions()) { + if (region->region().config().fill_pattern == ipLightning) { + Polygons lf = to_polygons(region->fill_surfaces().filter_by_type(stInternal)); + lightning_fill.insert(lightning_fill.end(), lf.begin(), lf.end()); + } + } + + if (lightning_fill.empty()) + continue; + + for (LayerRegion *region : layer->regions()) { + backup_surfaces[lidx][region] = std::move( + region->m_fill_surfaces); // Make backup copy by move!! so that pointers in candidate surfaces stay valid + // Copy the surfaces back, this will make copy, but we will later discard it anyway + region->m_fill_surfaces = backup_surfaces[lidx][region]; + } + + for (LayerRegion *region : layer->regions()) { + ExPolygons sparse_infill = to_expolygons(region->fill_surfaces().filter_by_type(stInternal)); + ExPolygons solid_infill = to_expolygons(region->fill_surfaces().filter_by_type(stInternalSolid)); + + if (sparse_infill.empty()) { + break; + } + for (const auto &surface : surfaces_by_layer[lidx]) { + if (surface.region != region) + continue; + ExPolygons expansion = intersection_ex(sparse_infill, expand(surface.new_polys, scaled(3.0))); + solid_infill.insert(solid_infill.end(), expansion.begin(), expansion.end()); + } + + solid_infill = union_safety_offset_ex(solid_infill); + sparse_infill = diff_ex(sparse_infill, solid_infill); + + region->m_fill_surfaces.remove_types({stInternalSolid, stInternal}); + for (const ExPolygon &ep : solid_infill) { + region->m_fill_surfaces.surfaces.emplace_back(stInternalSolid, ep); + } + for (const ExPolygon &ep : sparse_infill) { + region->m_fill_surfaces.surfaces.emplace_back(stInternal, ep); + } + } + } + }); + + // Use the modified surfaces to generate expanded lightning anchors + this->m_lightning_generator = this->prepare_lightning_infill_data(); + + // And now restore carefully the original surfaces, again using move to avoid reallocation and preserving the validity of the + // pointers in surface candidates + for (size_t lidx = 0; lidx < this->layer_count(); lidx++) { + Layer *layer = this->get_layer(lidx); + for (LayerRegion *region : layer->regions()) { + if (backup_surfaces[lidx].find(region) != backup_surfaces[lidx].end()) { + region->m_fill_surfaces = std::move(backup_surfaces[lidx][region]); + } + } + } + } + std::map infill_lines; // SECTION to generate infill polylines { @@ -1740,7 +1831,6 @@ void PrintObject::bridge_over_infill() } this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); - this->m_lightning_generator = this->prepare_lightning_infill_data(); std::vector layers_to_generate_infill; for (const auto &pair : surfaces_by_layer) { @@ -2018,6 +2108,8 @@ void PrintObject::bridge_over_infill() polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), [](const Line &s) { return s.a == s.b; }), polygon_sections[i].end()); + std::sort(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &a, const Line &b) { return a.a.y() < b.b.y(); }); } // reconstruct polygon from polygon sections @@ -2031,36 +2123,40 @@ void PrintObject::bridge_over_infill() for (const auto &polygon_slice : polygon_sections) { std::unordered_set used_segments; for (TracedPoly &traced_poly : current_traced_polys) { - auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(), - [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); + auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(), + [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); + auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(), + [](const Point &high, const Line &seg) { return seg.a.y() > high.y(); }); - if (maybe_first_overlap != polygon_slice.end() && // segment exists - segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(), - maybe_first_overlap->b.y())) // segment is overlapping - { - // Overlapping segment. In that case, add it - // to the traced polygon and add segment to used segments - if ((traced_poly.lows.back() - maybe_first_overlap->a).cast().squaredNorm() < + bool segment_added = false; + for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) { + if (used_segments.find(&(*candidate)) != used_segments.end()) { + continue; + } + + if ((traced_poly.lows.back() - candidate->a).cast().squaredNorm() < 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { - traced_poly.lows.push_back(maybe_first_overlap->a); + traced_poly.lows.push_back(candidate->a); } else { traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(maybe_first_overlap->a - Point{bridging_flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(maybe_first_overlap->a); + traced_poly.lows.push_back(candidate->a - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(candidate->a); } - if ((traced_poly.highs.back() - maybe_first_overlap->b).cast().squaredNorm() < + if ((traced_poly.highs.back() - candidate->b).cast().squaredNorm() < 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { - traced_poly.highs.push_back(maybe_first_overlap->b); + traced_poly.highs.push_back(candidate->b); } else { traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->b - Point{bridging_flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->b); + traced_poly.highs.push_back(candidate->b - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(candidate->b); } - used_segments.insert(&(*maybe_first_overlap)); - } else { - // Zero or multiple overlapping segments. Resolving this is nontrivial, - // so we just close this polygon and maybe open several new. This will hopefully happen much less often + segment_added = true; + used_segments.insert(&(*candidate)); + } + + if (!segment_added) { + // Zero overlapping segments, we just close this polygon traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); @@ -2278,43 +2374,73 @@ void PrintObject::bridge_over_infill() tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range r) { PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end()) + if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end() && surfaces_by_layer.find(lidx + 1) == surfaces_by_layer.end()) continue; Layer *layer = po->get_layer(lidx); Polygons cut_from_infill{}; - for (const auto &surface : surfaces_by_layer.at(lidx)) { - cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) { + for (const auto &surface : surfaces_by_layer.at(lidx)) { + cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + } + } + + Polygons additional_ensuring_areas{}; + if (surfaces_by_layer.find(lidx + 1) != surfaces_by_layer.end()) { + for (const auto &surface : surfaces_by_layer.at(lidx + 1)) { + auto additional_area = diff(surface.new_polys, + shrink(surface.new_polys, surface.region->flow(frSolidInfill).scaled_spacing())); + additional_ensuring_areas.insert(additional_ensuring_areas.end(), additional_area.begin(), additional_area.end()); + } } for (LayerRegion *region : layer->regions()) { Surfaces new_surfaces; - SurfacesPtr internal_infills = region->m_fill_surfaces.filter_by_type(stInternal); - ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); + Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces().surfaces))); + near_perimeters = diff(near_perimeters, shrink(near_perimeters, region->flow(frSolidInfill).scaled_spacing())); + ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters); + + SurfacesPtr internal_infills = region->m_fill_surfaces.filter_by_type(stInternal); + ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); + new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); for (const ExPolygon &ep : new_internal_infills) { - new_surfaces.emplace_back(*internal_infills.front(), ep); + new_surfaces.emplace_back(stInternal, ep); } SurfacesPtr internal_solids = region->m_fill_surfaces.filter_by_type(stInternalSolid); - for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) { - for (const Surface *surface : internal_solids) { - if (cs.original_surface == surface) { - Surface tmp{*surface, {}}; - tmp.surface_type = stInternalBridge; - tmp.bridge_angle = cs.bridge_angle; - for (const ExPolygon &ep : union_ex(cs.new_polys)) { - new_surfaces.emplace_back(tmp, ep); + if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) { + for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) { + for (const Surface *surface : internal_solids) { + if (cs.original_surface == surface) { + Surface tmp{*surface, {}}; + tmp.surface_type = stInternalBridge; + tmp.bridge_angle = cs.bridge_angle; + for (const ExPolygon &ep : union_ex(cs.new_polys)) { + new_surfaces.emplace_back(tmp, ep); + } + break; } - break; } } } - ExPolygons new_internal_solids = diff_ex(internal_solids, cut_from_infill); + ExPolygons new_internal_solids = to_expolygons(internal_solids); + new_internal_solids.insert(new_internal_solids.end(), additional_ensuring.begin(), additional_ensuring.end()); + new_internal_solids = diff_ex(new_internal_solids, cut_from_infill); + new_internal_solids = union_safety_offset_ex(new_internal_solids); for (const ExPolygon &ep : new_internal_solids) { - new_surfaces.emplace_back(*internal_solids.front(), ep); + new_surfaces.emplace_back(stInternalSolid, ep); } +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("Aensuring_" + std::to_string(reinterpret_cast(®ion)), to_polylines(additional_ensuring), + to_polylines(near_perimeters), to_polylines(to_polygons(internal_infills)), + to_polylines(to_polygons(internal_solids))); + debug_draw("Aensuring_" + std::to_string(reinterpret_cast(®ion)) + "_new", to_polylines(additional_ensuring), + to_polylines(near_perimeters), to_polylines(to_polygons(new_internal_infills)), + to_polylines(to_polygons(new_internal_solids))); +#endif + region->m_fill_surfaces.remove_types({stInternalSolid, stInternal}); region->m_fill_surfaces.append(new_surfaces); } diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index d53b3e785..7b11955d9 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -2249,6 +2249,7 @@ static void increase_areas_one_layer( // But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). parent.state.result_on_layer_reset(); + parent.state.to_model_gracious = false; #ifdef TREE_SUPPORTS_TRACK_LOST parent.state.verylost = true; #endif // TREE_SUPPORTS_TRACK_LOST @@ -4410,7 +4411,10 @@ static void draw_branches( // Don't propagate further than 1.5 * bottom radius. //LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height; LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height; - LayerIndex layer_bottommost = std::max(0, layer_begin - layers_propagate_max); + LayerIndex layer_bottommost = branch.path.front()->state.verylost ? + // If the tree bottom is hanging in the air, bring it down to some surface. + 0 : + std::max(0, layer_begin - layers_propagate_max); // Only propagate until the rest area is smaller than this threshold. double support_area_stop = 0.2 * M_PI * sqr(double(bottom_radius)); // Only propagate until the rest area is smaller than this threshold. diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index 899f02724..66010b5b0 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -91,7 +91,7 @@ struct AreaIncreaseSettings struct TreeSupportSettings; -// #define TREE_SUPPORTS_TRACK_LOST +#define TREE_SUPPORTS_TRACK_LOST // C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided. struct SupportElementStateBits { diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 60d89c9e9..602654633 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -36,6 +36,8 @@ #define ENABLE_MATRICES_DEBUG 0 // Shows an imgui dialog containing data from class ObjectManipulation #define ENABLE_OBJECT_MANIPULATION_DEBUG 0 +// Shows an imgui dialog containing data for class GLCanvas3D::SLAView +#define ENABLE_SLA_VIEW_DEBUG_WINDOW 0 // Enable rendering of objects using environment map diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index f43afdf42..92383dc97 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -439,6 +439,14 @@ void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& proj m_model_offset = to_3d(m_build_volume.bounding_volume2d().center(), -0.03); // register for picking + const std::vector>* const raycaster = wxGetApp().plater()->canvas3D()->get_raycasters_for_picking(SceneRaycaster::EType::Bed); + if (!raycaster->empty()) { + // The raycaster may have been set by the call to init_triangles() made from render_texture() if the printbed was + // changed while the camera was pointing upward. + // In this case we need to remove it before creating a new using the model geometry + wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed); + m_model.mesh_raycaster.reset(); + } register_raycasters_for_picking(m_model.model.get_geometry(), Geometry::translation_transform(m_model_offset)); // update extended bounding box diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 112edec3e..7c5008554 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -611,10 +611,28 @@ void GLVolumeCollection::load_object_auxiliary( if (convex_hull.has_value()) v.set_convex_hull(*convex_hull); v.is_modifier = false; - v.shader_outside_printer_detection_enabled = (step == slaposSupportTree); + v.shader_outside_printer_detection_enabled = (step == slaposSupportTree || step == slaposDrillHoles); v.set_instance_transformation(model_instance.get_transformation()); }; + if (milestone == SLAPrintObjectStep::slaposDrillHoles) { + if (print_object->get_parts_to_slice().size() > 1) { + // Get the mesh. + TriangleMesh backend_mesh; + std::shared_ptr preview_mesh_ptr = print_object->get_mesh_to_print(); + if (preview_mesh_ptr != nullptr) + backend_mesh = TriangleMesh(*preview_mesh_ptr); + if (!backend_mesh.empty()) { + backend_mesh.transform(mesh_trafo_inv); + TriangleMesh convex_hull = backend_mesh.convex_hull_3d(); + for (const std::pair& instance_idx : instances) { + const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; + add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposDrillHoles, backend_mesh, GLVolume::MODEL_COLOR[0], convex_hull); + } + } + } + } + // Get the support mesh. if (milestone == SLAPrintObjectStep::slaposSupportTree) { TriangleMesh supports_mesh = print_object->support_mesh(); @@ -622,8 +640,8 @@ void GLVolumeCollection::load_object_auxiliary( supports_mesh.transform(mesh_trafo_inv); TriangleMesh convex_hull = supports_mesh.convex_hull_3d(); for (const std::pair& instance_idx : instances) { - const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; - add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposSupportTree, supports_mesh, GLVolume::SLA_SUPPORT_COLOR, convex_hull); + const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; + add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposSupportTree, supports_mesh, GLVolume::SLA_SUPPORT_COLOR, convex_hull); } } } @@ -864,7 +882,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con } for (GLVolume* volume : volumes) { - if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0) + if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->is_sla_pad() || volume->is_sla_support()) continue; int extruder_id = volume->extruder_id - 1; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 866d5a817..d0b772197 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -3323,6 +3323,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , p(new priv(this)) { + wxBusyCursor wait; + this->SetFont(wxGetApp().normal_font()); p->load_vendors(); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index bd4653ab8..4e58e68ec 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -4066,69 +4066,58 @@ void GCodeViewer::render_legend(float& legend_height) } }; - auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) { - ImGuiIO& io = ImGui::GetIO(); - const ImTextureID tex_id = io.Fonts->TexID; - const float tex_w = static_cast(io.Fonts->TexWidth); - const float tex_h = static_cast(io.Fonts->TexHeight); - const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id); - const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h }; - const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; - window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f })); - }; - ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); - toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendTravel); + toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendTravel); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendWipe); + toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendWipe); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendRetract); + toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendRetract); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendDeretract); + toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendDeretract); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendSeams); + toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendSeams); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendToolChanges); + toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendToolChanges); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendColorChanges); + toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendColorChanges); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendPausePrints); + toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendPausePrints); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendCustomGCodes); + toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendCustomGCodes); }); ImGui::SameLine(); - toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendCOG); + toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendCOG); }); ImGui::SameLine(); if (!wxGetApp().is_gcode_viewer()) { - toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendShells); + toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendShells); }); ImGui::SameLine(); } - toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - image_icon(window, pos, size, ImGui::LegendToolMarker); + toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { + imgui.draw_icon(window, pos, size, ImGui::LegendToolMarker); }); bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3a56d90cb..9df4069a8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1127,6 +1127,250 @@ void GLCanvas3D::load_arrange_settings() m_arrange_settings_fff_seq_print.alignment = arr_alignment ; } +static std::vector processed_objects_idxs(const Model& model, const SLAPrint& sla_print, const GLVolumePtrs& volumes) +{ + std::vector ret; + GLVolumePtrs matching_volumes; + std::copy_if(volumes.begin(), volumes.end(), std::back_inserter(matching_volumes), [](GLVolume* v) { + return v->volume_idx() == -(int)slaposDrillHoles; }); + for (const GLVolume* v : matching_volumes) { + const int mo_idx = v->object_idx(); + const ModelObject* model_object = (mo_idx < (int)model.objects.size()) ? model.objects[mo_idx] : nullptr; + if (model_object != nullptr && model_object->instances[v->instance_idx()]->is_printable()) { + const SLAPrintObject* print_object = sla_print.get_print_object_by_model_object_id(model_object->id()); + if (print_object != nullptr && print_object->get_parts_to_slice().size() > 1) + ret.push_back(mo_idx); + } + } + std::sort(ret.begin(), ret.end()); + ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); + return ret; +}; + +static bool composite_id_match(const GLVolume::CompositeID& id1, const GLVolume::CompositeID& id2) +{ + return id1.object_id == id2.object_id && id1.instance_id == id2.instance_id; +} + +static bool object_contains_negative_volumes(const Model& model, int obj_id) { + return (0 <= obj_id && obj_id < (int)model.objects.size()) ? model.objects[obj_id]->has_negative_volume_mesh() : false; +} + +void GLCanvas3D::SLAView::detect_type_from_volumes(const GLVolumePtrs& volumes) +{ + for (auto& [id, type] : m_instances_cache) { + type = ESLAViewType::Original; + } + + for (const GLVolume* v : volumes) { + if (v->volume_idx() == -(int)slaposDrillHoles) { + if (object_contains_negative_volumes(*m_parent.get_model(), v->composite_id.object_id)) { + const InstancesCacheItem* instance = find_instance_item(v->composite_id); + assert(instance != nullptr); + set_type(instance->first, ESLAViewType::Processed); + } + } + } +} + +void GLCanvas3D::SLAView::set_type(ESLAViewType new_type) +{ + for (auto& [id, type] : m_instances_cache) { + type = new_type; + if (new_type == ESLAViewType::Processed) + select_full_instance(id); + } +} + +void GLCanvas3D::SLAView::set_type(const GLVolume::CompositeID& id, ESLAViewType new_type) +{ + InstancesCacheItem* instance = find_instance_item(id); + assert(instance != nullptr); + instance->second = new_type; + if (new_type == ESLAViewType::Processed) + select_full_instance(id); +} + +void GLCanvas3D::SLAView::update_volumes_visibility(GLVolumePtrs& volumes) +{ + const SLAPrint* sla_print = m_parent.sla_print(); + const std::vector mo_idxs = (sla_print != nullptr) ? processed_objects_idxs(*m_parent.get_model(), *sla_print, volumes) : std::vector(); + + std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); + + for (GLVolume* v : volumes) { + const int obj_idx = v->object_idx(); + bool active = std::find(mo_idxs.begin(), mo_idxs.end(), obj_idx) == mo_idxs.end(); + if (!active) { + const InstancesCacheItem* instance = find_instance_item(v->composite_id); + assert(instance != nullptr); + active = (instance->second == ESLAViewType::Processed) ? v->volume_idx() < 0 : v->volume_idx() != -(int)slaposDrillHoles; + } + v->is_active = active; + auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr item) { return item->get_raycaster() == v->mesh_raycaster.get(); }); + if (it != raycasters->end()) + (*it)->set_active(v->is_active); + } +} + +void GLCanvas3D::SLAView::update_instances_cache(const std::vector>& new_to_old_ids_map) +{ + // First, extract current instances list from the volumes + const GLVolumePtrs& volumes = m_parent.get_volumes().volumes; + std::vector new_instances_cache; + for (const GLVolume* v : volumes) { + new_instances_cache.emplace_back(v->composite_id, ESLAViewType::Original); + } + + std::sort(new_instances_cache.begin(), new_instances_cache.end(), + [](const InstancesCacheItem& i1, const InstancesCacheItem& i2) { + return i1.first.object_id < i2.first.object_id || (i1.first.object_id == i2.first.object_id && i1.first.instance_id < i2.first.instance_id); }); + + new_instances_cache.erase(std::unique(new_instances_cache.begin(), new_instances_cache.end(), + [](const InstancesCacheItem& i1, const InstancesCacheItem& i2) { + return composite_id_match(i1.first, i2.first); }), new_instances_cache.end()); + + // Second, update instances type from previous state + for (auto& inst_type : new_instances_cache) { + const auto map_to_old_it = std::find_if(new_to_old_ids_map.begin(), new_to_old_ids_map.end(), [&inst_type](const std::pair& item) { + return composite_id_match(inst_type.first, item.first); }); + + const GLVolume::CompositeID old_inst_id = (map_to_old_it != new_to_old_ids_map.end()) ? map_to_old_it->second : inst_type.first; + const InstancesCacheItem* old_instance = find_instance_item(old_inst_id); + if (old_instance != nullptr) + inst_type.second = old_instance->second; + } + + m_instances_cache = new_instances_cache; +} + +void GLCanvas3D::SLAView::render_switch_button() +{ + const SLAPrint* sla_print = m_parent.sla_print(); + if (sla_print == nullptr) + return; + + const std::vector mo_idxs = processed_objects_idxs(*m_parent.get_model(), *sla_print, m_parent.get_volumes().volumes); + if (mo_idxs.empty()) + return; + + Selection& selection = m_parent.get_selection(); + const int obj_idx = selection.get_object_idx(); + if (std::find(mo_idxs.begin(), mo_idxs.end(), obj_idx) == mo_idxs.end()) + return; + + if (!object_contains_negative_volumes(*m_parent.get_model(), obj_idx)) + return; + + const int inst_idx = selection.get_instance_idx(); + if (inst_idx < 0) + return; + + const GLVolume::CompositeID composite_id(obj_idx, 0, inst_idx); + const InstancesCacheItem* sel_instance = find_instance_item(composite_id); + if (sel_instance == nullptr) + return; + + const ESLAViewType type = sel_instance->second; + + BoundingBoxf ss_box; + if (m_use_instance_bbox) { + const Selection::EMode mode = selection.get_mode(); + if (obj_idx >= 0 && inst_idx >= 0) { + const Selection::IndicesList selected_idxs = selection.get_volume_idxs(); + std::vector idxs_as_vector; + idxs_as_vector.assign(selected_idxs.begin(), selected_idxs.end()); + selection.add_instance(obj_idx, inst_idx, true); + ss_box = selection.get_screen_space_bounding_box(); + selection.add_volumes(mode, idxs_as_vector, true); + } + } + + if (!ss_box.defined) + ss_box = selection.get_screen_space_bounding_box(); + assert(ss_box.defined); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::SetNextWindowPos(ImVec2((float)ss_box.max.x(), (float)ss_box.center().y()), ImGuiCond_Always, ImVec2(0.0, 0.5)); + imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); + const float icon_size = 1.5 * ImGui::GetTextLineHeight(); + if (imgui.draw_radio_button(_u8L("SLA view"), 1.5f * icon_size, true, + [this, &imgui, sel_instance](ImGuiWindow& window, const ImVec2& pos, float size) { + const wchar_t icon_id = (sel_instance->second == ESLAViewType::Original) ? ImGui::SlaViewProcessed : ImGui::SlaViewOriginal; + imgui.draw_icon(window, pos, size, icon_id); + })) { + switch (sel_instance->second) + { + case ESLAViewType::Original: { m_parent.set_sla_view_type(sel_instance->first, ESLAViewType::Processed); break; } + case ESLAViewType::Processed: { m_parent.set_sla_view_type(sel_instance->first, ESLAViewType::Original); break; } + default: { assert(false); break; } + } + } + + if (ImGui::IsItemHovered()) { + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + wxString tooltip; + switch (type) + { + case ESLAViewType::Original: { tooltip = _L("Show as processed"); break; } + case ESLAViewType::Processed: { tooltip = _L("Show as original"); break; } + default: { assert(false); break; } + } + + imgui.text(tooltip); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + } + imgui.end(); + ImGui::PopStyleColor(2); +} + +#if ENABLE_SLA_VIEW_DEBUG_WINDOW +void GLCanvas3D::SLAView::render_debug_window() +{ + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("SLAView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); + for (const auto& [id, type] : m_instances_cache) { + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "(" + std::to_string(id.object_id) + ", " + std::to_string(id.instance_id) + ")"); + ImGui::SameLine(); + imgui.text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), (type == ESLAViewType::Original) ? "Original" : "Processed"); + } + if (!m_instances_cache.empty()) + ImGui::Separator(); + + imgui.checkbox("Use instance bounding box", m_use_instance_bbox); + imgui.end(); +} +#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW + +GLCanvas3D::SLAView::InstancesCacheItem* GLCanvas3D::SLAView::find_instance_item(const GLVolume::CompositeID& id) +{ + auto it = std::find_if(m_instances_cache.begin(), m_instances_cache.end(), + [&id](const InstancesCacheItem& item) { return composite_id_match(item.first, id); }); + return (it == m_instances_cache.end()) ? nullptr : &(*it); +} + +void GLCanvas3D::SLAView::select_full_instance(const GLVolume::CompositeID& id) +{ + bool extended_selection = false; + Selection& selection = m_parent.get_selection(); + const Selection::ObjectIdxsToInstanceIdxsMap& sel_cache = selection.get_content(); + auto obj_it = sel_cache.find(id.object_id); + if (obj_it != sel_cache.end()) { + auto inst_it = std::find(obj_it->second.begin(), obj_it->second.end(), id.instance_id); + if (inst_it != obj_it->second.end()) { + selection.add_instance(id.object_id, id.instance_id); + extended_selection = true; + } + } + + if (extended_selection) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); +} + PrinterTechnology GLCanvas3D::current_printer_technology() const { return m_process->current_printer_technology(); @@ -1168,6 +1412,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) , m_render_sla_auxiliaries(true) , m_labels(*this) , m_slope(m_volumes) + , m_sla_view(*this) { if (m_canvas != nullptr) { m_timer.SetOwner(m_canvas); @@ -1774,6 +2019,15 @@ void GLCanvas3D::render() wxGetApp().obj_manipul()->render_debug_window(); #endif // ENABLE_OBJECT_MANIPULATION_DEBUG + if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() == ptSLA) { + const GLGizmosManager::EType type = m_gizmos.get_current_type(); + if (type == GLGizmosManager::EType::Undefined) + m_sla_view.render_switch_button(); +#if ENABLE_SLA_VIEW_DEBUG_WINDOW + m_sla_view.render_debug_window(); +#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW + } + std::string tooltip; // Negative coordinate means out of the window, likely because the window was deactivated. @@ -2013,6 +2267,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re size_t volume_idx; }; + std::vector> new_to_old_ids_map; + // SLA steps to pull the preview meshes for. typedef std::array SLASteps; SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad }; @@ -2160,12 +2416,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { const ModelObject &model_object = *m_model->objects[obj_idx]; for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { - const ModelVolume &model_volume = *model_object.volumes[volume_idx]; + const ModelVolume &model_volume = *model_object.volumes[volume_idx]; for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { - const ModelInstance &model_instance = *model_object.instances[instance_idx]; - ModelVolumeState key(model_volume.id(), model_instance.id()); - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + const ModelInstance &model_instance = *model_object.instances[instance_idx]; + ModelVolumeState key(model_volume.id(), model_instance.id()); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); if (it->new_geometry()) { // New volume. auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower); @@ -2178,15 +2434,17 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx); m_volumes.volumes.back()->geometry_id = key.geometry_id; update_object_list = true; - } else { - // Recycling an old GLVolume. - GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; + } + else { + // Recycling an old GLVolume. + GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; assert(existing_volume.geometry_id == key.geometry_id); - // Update the Object/Volume/Instance indices into the current Model. - if (existing_volume.composite_id != it->composite_id) { - existing_volume.composite_id = it->composite_id; - update_object_list = true; - } + // Update the Object/Volume/Instance indices into the current Model. + if (existing_volume.composite_id != it->composite_id) { + new_to_old_ids_map.push_back(std::make_pair(it->composite_id, existing_volume.composite_id)); + existing_volume.composite_id = it->composite_id; + update_object_list = true; + } } } } @@ -2237,7 +2495,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } else { // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. - m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + const GLVolume::CompositeID new_id(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + new_to_old_ids_map.push_back(std::make_pair(new_id, m_volumes.volumes[it->volume_idx]->composite_id)); + m_volumes.volumes[it->volume_idx]->composite_id = new_id; m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); } } @@ -2305,6 +2565,24 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re else m_selection.volumes_changed(map_glvolume_old_to_new); + if (printer_technology == ptSLA) { + std::sort(new_to_old_ids_map.begin(), new_to_old_ids_map.end(), + [](const std::pair& i1, const std::pair& i2) { + return i1.first.object_id < i2.first.object_id || (i1.first.object_id == i2.first.object_id && i1.first.instance_id < i2.first.instance_id); }); + + new_to_old_ids_map.erase(std::unique(new_to_old_ids_map.begin(), new_to_old_ids_map.end(), + [](const std::pair& i1, const std::pair& i2) { + return composite_id_match(i1.first, i2.first); }), new_to_old_ids_map.end()); + + m_sla_view.update_instances_cache(new_to_old_ids_map); + if (m_sla_view_type_detection_active) { + m_sla_view.detect_type_from_volumes(m_volumes.volumes); + m_sla_view_type_detection_active = false; + } + m_sla_view.update_volumes_visibility(m_volumes.volumes); + update_object_list = true; + } + m_gizmos.update_data(); m_gizmos.refresh_on_off_state(); @@ -4387,6 +4665,20 @@ std::pair> GLCanvas3D::get_layers_h return ret; } +void GLCanvas3D::set_sla_view_type(ESLAViewType type) +{ + m_sla_view.set_type(type); + m_sla_view.update_volumes_visibility(m_volumes.volumes); + m_dirty = true; +} + +void GLCanvas3D::set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type) +{ + m_sla_view.set_type(id, type); + m_sla_view.update_volumes_visibility(m_volumes.volumes); + m_dirty = true; +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index b26be314e..023540068 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -466,6 +466,12 @@ public: int alignment = 0; }; + enum class ESLAViewType + { + Original, + Processed + }; + private: wxGLCanvas* m_canvas; wxGLContext* m_context; @@ -545,11 +551,37 @@ private: bool m_tooltip_enabled{ true }; Slope m_slope; + class SLAView + { + public: + explicit SLAView(GLCanvas3D& parent) : m_parent(parent) {} + void detect_type_from_volumes(const GLVolumePtrs& volumes); + void set_type(ESLAViewType type); + void set_type(const GLVolume::CompositeID& id, ESLAViewType type); + void update_volumes_visibility(GLVolumePtrs& volumes); + void update_instances_cache(const std::vector>& new_to_old_ids_map); + void render_switch_button(); + +#if ENABLE_SLA_VIEW_DEBUG_WINDOW + void render_debug_window(); +#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW + + private: + GLCanvas3D& m_parent; + typedef std::pair InstancesCacheItem; + std::vector m_instances_cache; + bool m_use_instance_bbox{ true }; + + InstancesCacheItem* find_instance_item(const GLVolume::CompositeID& id); + void select_full_instance(const GLVolume::CompositeID& id); + }; + + SLAView m_sla_view; + bool m_sla_view_type_detection_active{ false }; + ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla, m_arrange_settings_fff_seq_print; - PrinterTechnology current_printer_technology() const; - bool is_arrange_alignment_enabled() const; template @@ -666,7 +698,7 @@ private: GLModel m_background; public: - explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed); + GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed); ~GLCanvas3D(); bool is_initialized() const { return m_initialized; } @@ -782,6 +814,8 @@ public: void zoom_to_gcode(); void select_view(const std::string& direction); + PrinterTechnology current_printer_technology() const; + void update_volumes_colors_by_extruder(); bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); } @@ -968,6 +1002,10 @@ public: std::pair> get_layers_height_data(int object_id); + void set_sla_view_type(ESLAViewType type); + void set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type); + void enable_sla_view_type_detection() { m_sla_view_type_detection_active = true; } + private: bool _is_shown_on_screen() const; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b0411c89c..83c3cf13f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3057,6 +3057,13 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage auto wizard = new ConfigWizard(mainframe); const bool res = wizard->run(reason, start_page); + // !!! Deallocate memory after close ConfigWizard. + // Note, that mainframe is a parent of ConfigWizard. + // So, wizard will be destroyed only during destroying of mainframe + // To avoid this state the wizard have to be disconnected from mainframe and Destroyed explicitly + mainframe->RemoveChild(wizard); + wizard->Destroy(); + if (res) { load_current_presets(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4fa9d6a16..9cbcbc323 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -758,6 +758,10 @@ void ObjectList::selection_changed() wxGetApp().obj_layers()->update_scene_from_editor_selection(); } } + else if (type & itVolume) { + if (printer_technology() == ptSLA) + wxGetApp().plater()->canvas3D()->set_sla_view_type(scene_selection().get_first_volume()->composite_id, GLCanvas3D::ESLAViewType::Original); + } } part_selection_changed(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 9dacee561..ae40e0f76 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -15,6 +15,8 @@ #include "MainFrame.hpp" #include "MsgDialog.hpp" +#include + #include #include "slic3r/Utils/FixModelByWin10.hpp" @@ -519,19 +521,18 @@ void ObjectManipulation::UpdateAndShow(const bool show) OG_Settings::UpdateAndShow(show); } -void ObjectManipulation::Enable(const bool enadle) +void ObjectManipulation::Enable(const bool enable) { - for (auto editor : m_editors) - editor->Enable(enadle); + m_is_enabled = m_is_enabled_size_and_scale = enable; for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt , m_reset_skew_button }) - win->Enable(enadle); + win->Enable(enable); } void ObjectManipulation::DisableScale() { - for (auto editor : m_editors) - editor->Enable(editor->has_opt_key("scale") || editor->has_opt_key("size") ? false : true); + m_is_enabled = true; + m_is_enabled_size_and_scale = false; for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_lock_bnt, m_reset_skew_button }) win->Enable(false); } @@ -1229,6 +1230,12 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, this->SetSelection(-1, -1); //select all event.Skip(); })); + + this->Bind(wxEVT_UPDATE_UI, [parent, this](wxUpdateUIEvent& evt) { + const bool is_gizmo_in_editing_mode = wxGetApp().plater()->canvas3D()->get_gizmos_manager().is_in_editing_mode(); + const bool is_enabled_editing = has_opt_key("scale") || has_opt_key("size") ? parent->is_enabled_size_and_scale() : true; + evt.Enable(!is_gizmo_in_editing_mode && parent->is_enabled() && is_enabled_editing); + }); } void ManipulationEditor::msw_rescale() diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 428ffc24b..139171d99 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -165,6 +165,11 @@ private: std::vector m_editors; + // parameters for enabling/disabling of editors + bool m_is_enabled { true }; + bool m_is_enabled_size_and_scale { true }; + + public: ObjectManipulation(wxWindow* parent); ~ObjectManipulation() {} @@ -213,6 +218,9 @@ public: static wxString coordinate_type_str(ECoordinatesType type); + bool is_enabled() const { return m_is_enabled; } + bool is_enabled_size_and_scale()const { return m_is_enabled_size_and_scale; } + #if ENABLE_OBJECT_MANIPULATION_DEBUG void render_debug_window(); #endif // ENABLE_OBJECT_MANIPULATION_DEBUG diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp index 0688ca5b2..8451679dd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp @@ -20,8 +20,15 @@ GLGizmoSlaBase::GLGizmoSlaBase(GLCanvas3D& parent, const std::string& icon_filen void GLGizmoSlaBase::reslice_until_step(SLAPrintObjectStep step, bool postpone_error_messages) { wxGetApp().CallAfter([this, step, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages); - }); + if (m_c->selection_info()) + wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages); + else { + const Selection& selection = m_parent.get_selection(); + const int object_idx = selection.get_object_idx(); + if (object_idx >= 0 && !selection.is_wipe_tower()) + wxGetApp().plater()->reslice_SLA_until_step(step, *wxGetApp().plater()->model().objects[object_idx], postpone_error_messages); + } + }); } CommonGizmosDataID GLGizmoSlaBase::on_get_requirements() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index b9ec5cf19..e7cd63fec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -87,6 +87,8 @@ void GLGizmoSlaSupports::data_changed(bool is_serializing) register_point_raycasters_for_picking(); else update_point_raycasters_for_picking_transform(); + + m_c->instances_hider()->set_hide_full_scene(true); } // m_parent.toggle_model_objects_visibility(false); @@ -399,7 +401,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } if (action == SLAGizmoEventType::DiscardChanges) { - ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, + ask_about_changes([this](){ editing_mode_apply_changes(); }, [this](){ editing_mode_discard_changes(); }); return true; } @@ -816,39 +818,36 @@ std::string GLGizmoSlaSupports::on_get_name() const return _u8L("SLA Support Points"); } -void GLGizmoSlaSupports::ask_about_changes_call_after(std::function on_yes, std::function on_no) +bool GLGizmoSlaSupports::ask_about_changes(std::function on_yes, std::function on_no) { - wxGetApp().CallAfter([on_yes, on_no]() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " - "edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL ); - int ret = dlg.ShowModal(); - if (ret == wxID_YES) - on_yes(); - else if (ret == wxID_NO) - on_no(); - }); -} + MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually edited support points?") + "\n", + _L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL ); + const int ret = dlg.ShowModal(); + if (ret == wxID_YES) + on_yes(); + else if (ret == wxID_NO) + on_no(); + else + return false; + + return true; +} void GLGizmoSlaSupports::on_set_state() { - if (m_state == m_old_state) - return; - - if (m_state == On && m_old_state != On) { // the gizmo was just turned on + if (m_state == On) { // the gizmo was just turned on // Set default head diameter from config. const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable(); - if (will_ask) { - ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, - [this](){ editing_mode_discard_changes(); }); - // refuse to be turned off so the gizmo is active when the CallAfter is executed - m_state = m_old_state; + else { + if (m_editing_mode && unsaved_changes() && on_is_activable()) { + if (!ask_about_changes([this]() { editing_mode_apply_changes(); }, + [this]() { editing_mode_discard_changes(); })) { + m_state = On; + return; + } } else { // we are actually shutting down @@ -856,16 +855,12 @@ void GLGizmoSlaSupports::on_set_state() m_old_mo_id = -1; } - if (m_state == Off) { - m_c->instances_hider()->set_hide_full_scene(false); - m_c->selection_info()->set_use_shift(false); // see top of on_render for details - } + m_c->instances_hider()->set_hide_full_scene(false); + m_c->selection_info()->set_use_shift(false); // see top of on_render for details + } - m_old_state = m_state; } - - void GLGizmoSlaSupports::on_start_dragging() { if (m_hover_id != -1) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index c098905aa..1c02b6aeb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -110,7 +110,6 @@ private: bool m_wait_for_up_event = false; bool m_selection_empty = true; - EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) std::vector get_config_options(const std::vector& keys) const; bool is_mesh_point_clipped(const Vec3d& point) const; @@ -131,7 +130,9 @@ private: void auto_generate(); void switch_to_editing_mode(); void disable_editing_mode(); - void ask_about_changes_call_after(std::function on_yes, std::function on_no); + + // return false if Cancel was selected + bool ask_about_changes(std::function on_yes, std::function on_no); protected: void on_set_state() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 98dd15137..ad67b99bb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -173,7 +173,8 @@ void GLGizmosManager::reset_all_states() const EType current = get_current_type(); if (current != Undefined) // close any open gizmo - open_gizmo(current); + if (!open_gizmo(current)) + return; activate_gizmo(Undefined); m_hover = Undefined; @@ -978,6 +979,9 @@ bool GLGizmosManager::activate_gizmo(EType type) return false; // gizmo refused to be turned on. } + if (m_parent.current_printer_technology() == ptSLA) + m_parent.set_sla_view_type(GLCanvas3D::ESLAViewType::Original); + new_gizmo.register_raycasters_for_picking(); // sucessful activation of gizmo diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index f2e6b0287..841ae2048 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -104,6 +104,8 @@ static const std::map font_icons_large = { {ImGui::PauseHoverButton , "notification_pause_hover" }, {ImGui::OpenButton , "notification_open" }, {ImGui::OpenHoverButton , "notification_open_hover" }, + {ImGui::SlaViewOriginal , "sla_view_original" }, + {ImGui::SlaViewProcessed , "sla_view_processed" }, }; static const std::map font_icons_extra_large = { @@ -490,6 +492,18 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active) return ImGui::RadioButton(label_utf8.c_str(), active); } +void ImGuiWrapper::draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id) +{ + ImGuiIO& io = ImGui::GetIO(); + const ImTextureID tex_id = io.Fonts->TexID; + const float tex_w = static_cast(io.Fonts->TexWidth); + const float tex_h = static_cast(io.Fonts->TexHeight); + const ImFontAtlas::CustomRect* const rect = GetTextureCustomRect(icon_id); + const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h }; + const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; + window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f })); +} + bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback) { diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 077bf568d..d95b934f1 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -92,9 +92,10 @@ public: void end(); bool button(const wxString &label, const wxString& tooltip = {}); - bool button(const wxString& label, float width, float height); + bool button(const wxString& label, float width, float height); bool button(const wxString& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f) bool radio_button(const wxString &label, bool active); + void draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id); bool draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback); bool checkbox(const wxString &label, bool &value); static void text(const char *label); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d9c96fc35..944fd0015 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1068,12 +1068,6 @@ void Sidebar::update_presets(Preset::Type preset_type) dynamic_cast(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); const size_t filament_cnt = p->combos_filament.size() > extruder_cnt ? extruder_cnt : p->combos_filament.size(); - if (filament_cnt == 1) { - // Single filament printer, synchronize the filament presets. - const std::string &name = preset_bundle.filaments.get_selected_preset_name(); - preset_bundle.set_filament_preset(0, name); - } - for (size_t i = 0; i < filament_cnt; i++) p->combos_filament[i]->update(); @@ -1417,14 +1411,14 @@ void Sidebar::update_sliced_info_sizer() new_label = _L("Used Filament (g)"); info_text = wxString::Format("%.2f", ps.total_weight); - const std::vector& filament_presets = wxGetApp().preset_bundle->filament_presets; + const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments; const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; if (ps.filament_stats.size() > 1) new_label += ":"; for (auto filament : ps.filament_stats) { - const Preset* filament_preset = filaments.find_preset(filament_presets[filament.first], false); + const Preset* filament_preset = filaments.find_preset(extruders_filaments[filament.first].get_selected_preset_name(), false); if (filament_preset) { double filament_weight; if (ps.filament_stats.size() == 1) @@ -2405,8 +2399,8 @@ void Plater::check_selected_presets_visibility(PrinterTechnology loaded_printer_ PresetBundle* preset_bundle = wxGetApp().preset_bundle; if (loaded_printer_technology == ptFFF) { update_selected_preset_visibility(preset_bundle->prints, names); - for (const std::string& filament : preset_bundle->filament_presets) { - Preset* preset = preset_bundle->filaments.find_preset(filament); + for (const auto& extruder_filaments : preset_bundle->extruders_filaments) { + Preset* preset = preset_bundle->filaments.find_preset(extruder_filaments.get_selected_preset_name()); if (preset && !preset->is_visible) { preset->is_visible = true; names.emplace_back(preset->name); @@ -4032,19 +4026,25 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) //! combo->GetStringSelection().ToUTF8().data()); std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, - Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data())); - - if (preset_type == Preset::TYPE_FILAMENT) { - wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); - } + Preset::remove_suffix_modified(into_u8(combo->GetString(selection))), idx); std::string last_selected_ph_printer_name = combo->get_selected_ph_printer_name(); bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); // TODO: ? - if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { - // Only update the plater UI for the 2nd and other filaments. - combo->update(); + if (preset_type == Preset::TYPE_FILAMENT) { + wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); + + TabFilament* tab = dynamic_cast(wxGetApp().get_tab(Preset::TYPE_FILAMENT)); + if (tab && combo->get_extruder_idx() == tab->get_active_extruder() && !tab->select_preset(preset_name)) { + // revert previously selection + const std::string& old_name = wxGetApp().preset_bundle->filaments.get_edited_preset().name; + wxGetApp().preset_bundle->set_filament_preset(idx, old_name); + combo->update(); + } + else + // Synchronize config.ini with the current selections. + wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); } else if (select_preset) { wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); @@ -4094,8 +4094,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data) if (view3D->is_dragging()) delayed_scene_refresh = true; - else + else { + view3D->get_canvas3d()->enable_sla_view_type_detection(); this->update_sla_scene(); + } break; default: break; } @@ -6897,6 +6899,8 @@ void Plater::on_extruders_change(size_t num_extruders) if (num_extruders == choices.size()) return; + dynamic_cast(wxGetApp().get_tab(Preset::TYPE_FILAMENT))->update_extruder_combobox(); + wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/); size_t i = choices.size(); @@ -6923,16 +6927,16 @@ bool Plater::update_filament_colors_in_full_config() // There is a case, when we use filament_color instead of extruder_color (when extruder_color == ""). // Thus plater config option "filament_colour" should be filled with filament_presets values. // Otherwise, on 3dScene will be used last edited filament color for all volumes with extruder_color == "". - const std::vector filament_presets = wxGetApp().preset_bundle->filament_presets; - if (filament_presets.size() == 1 || !p->config->has("filament_colour")) + const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments; + if (extruders_filaments.size() == 1 || !p->config->has("filament_colour")) return false; const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; std::vector filament_colors; - filament_colors.reserve(filament_presets.size()); + filament_colors.reserve(extruders_filaments.size()); - for (const std::string& filament_preset : filament_presets) - filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0)); + for (const auto& extr_filaments : extruders_filaments) + filament_colors.push_back(filaments.find_preset(extr_filaments.get_selected_preset_name(), true)->config.opt_string("filament_colour", (unsigned)0)); p->config->option("filament_colour")->values = filament_colors; return true; @@ -6957,10 +6961,12 @@ void Plater::on_config_change(const DynamicPrintConfig &config) p->config->set_key_value(opt_key, config.option(opt_key)->clone()); if (opt_key == "printer_technology") { - this->set_printer_technology(config.opt_enum(opt_key)); + const PrinterTechnology printer_technology = config.opt_enum(opt_key); + this->set_printer_technology(printer_technology); p->sidebar->show_sliced_info_sizer(false); p->reset_gcode_toolpaths(); p->view3D->get_canvas3d()->reset_sequential_print_clearance(); + p->view3D->get_canvas3d()->set_sla_view_type(GLCanvas3D::ESLAViewType::Original); } else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") { bed_shape_changed = true; @@ -7025,16 +7031,17 @@ void Plater::force_filament_colors_update() { bool update_scheduled = false; DynamicPrintConfig* config = p->config; - const std::vector filament_presets = wxGetApp().preset_bundle->filament_presets; - if (filament_presets.size() > 1 && - p->config->option("filament_colour")->values.size() == filament_presets.size()) + + const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments; + if (extruders_filaments.size() > 1 && + p->config->option("filament_colour")->values.size() == extruders_filaments.size()) { const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; std::vector filament_colors; - filament_colors.reserve(filament_presets.size()); + filament_colors.reserve(extruders_filaments.size()); - for (const std::string& filament_preset : filament_presets) - filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0)); + for (const auto& extr_filaments : extruders_filaments) + filament_colors.push_back(extr_filaments.get_selected_preset()->config.opt_string("filament_colour", (unsigned)0)); if (config->option("filament_colour")->values != filament_colors) { config->option("filament_colour")->values = filament_colors; @@ -7051,6 +7058,20 @@ void Plater::force_filament_colors_update() this->p->schedule_background_process(); } +void Plater::force_filament_cb_update() +{ + // Update visibility for templates presets according to app_config + PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + AppConfig& config = *wxGetApp().app_config; + for (Preset& preset : filaments) + preset.set_visible_from_appconfig(config); + wxGetApp().preset_bundle->update_compatible(PresetSelectCompatibleType::Never, PresetSelectCompatibleType::OnlyIfWasCompatible); + + // Update preset comboboxes on sidebar and filaments tab + p->sidebar->update_presets(Preset::TYPE_FILAMENT); + wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filaments.get_selected_preset_name()); +} + void Plater::force_print_bed_update() { // Fill in the printer model key with something which cannot possibly be valid, so that Plater::on_config_change() will update the print bed diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a76ef6f1c..58a840a4b 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -308,6 +308,7 @@ public: bool update_filament_colors_in_full_config(); void on_config_change(const DynamicPrintConfig &config); void force_filament_colors_update(); + void force_filament_cb_update(); void force_print_bed_update(); // On activating the parent window. void on_activate(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index bf1169d82..d8ce6bd4e 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -694,8 +694,6 @@ void PreferencesDialog::accept(wxEvent&) #endif // __linux__ } - bool update_filament_sidebar = (m_values.find("no_templates") != m_values.end()); - std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled", "font_size" }; for (const std::string& option : options_to_recreate_GUI) { @@ -761,12 +759,12 @@ void PreferencesDialog::accept(wxEvent&) wxGetApp().force_menu_update(); #endif //_MSW_DARK_MODE #endif // _WIN32 - + + if (m_values.find("no_templates") != m_values.end()) + wxGetApp().plater()->force_filament_cb_update(); + wxGetApp().update_ui_from_settings(); clear_cache(); - - if (update_filament_sidebar) - wxGetApp().plater()->sidebar().update_presets(Preset::Type::TYPE_FILAMENT); } void PreferencesDialog::revert(wxEvent&) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 4d703bd08..f5496833a 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -585,10 +585,10 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset if (m_type == Preset::TYPE_FILAMENT) { Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { - const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); + const Filament* selected_filament = m_preset_bundle->extruders_filaments[m_extruder_idx].get_selected_filament(); // Wide icons are shown if the currently selected preset is not compatible with the current printer, // and red flag is drown in front of the selected preset. - bool wide_icons = selected_preset && !selected_preset->is_compatible; + const bool wide_icons = selected_filament && !selected_filament->is_compatible; float scale = m_em_unit*0.1f; int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0; @@ -686,22 +686,16 @@ void PlaterPresetComboBox::switch_to_tab() if (int page_id = wxGetApp().tab_panel()->FindPage(tab); page_id != wxNOT_FOUND) { + //In a case of a multi-material printing, for editing another Filament Preset + //it's needed to select this preset for the "Filament settings" Tab + if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1 && + !dynamic_cast(wxGetApp().get_tab(m_type))->set_active_extruder(m_extruder_idx)) + // do nothing, if we can't set new extruder and select new preset + return; + wxGetApp().tab_panel()->SetSelection(page_id); // Switch to Settings NotePad wxGetApp().mainframe->select_tab(); - - //In a case of a multi-material printing, for editing another Filament Preset - //it's needed to select this preset for the "Filament settings" Tab - if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) - { - const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); - // Call select_preset() only if there is new preset and not just modified - if (!boost::algorithm::ends_with(selected_preset, Preset::suffix_modified())) - { - const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset); - wxGetApp().get_tab(m_type)->select_preset(preset_name); - } - } } } @@ -808,7 +802,7 @@ void PlaterPresetComboBox::update() { if (m_type == Preset::TYPE_FILAMENT && (m_preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA || - m_preset_bundle->filament_presets.size() <= (size_t)m_extruder_idx) ) + m_preset_bundle->extruders_filaments.size() <= (size_t)m_extruder_idx) ) return; // Otherwise fill in the list from scratch. @@ -816,6 +810,8 @@ void PlaterPresetComboBox::update() this->Clear(); invalidate_selection(); + const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_extruder_idx >= 0 ? m_extruder_idx : 0]; + const Preset* selected_filament_preset = nullptr; std::string extruder_color; if (m_type == Preset::TYPE_FILAMENT) { @@ -823,21 +819,23 @@ void PlaterPresetComboBox::update() if (!can_decode_color(extruder_color)) // Extruder color is not defined. extruder_color.clear(); - selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); + selected_filament_preset = extruder_filaments.get_selected_preset(); assert(selected_filament_preset); } - bool has_selection = m_collection->get_selected_idx() != size_t(-1); - const Preset* selected_preset = m_type == Preset::TYPE_FILAMENT ? selected_filament_preset : has_selection ? &m_collection->get_selected_preset() : nullptr; // Show wide icons if the currently selected preset is not compatible with the current printer, // and draw a red flag in front of the selected preset. - bool wide_icons = selected_preset && !selected_preset->is_compatible; + bool wide_icons = m_type == Preset::TYPE_FILAMENT ? + extruder_filaments.get_selected_filament() && !extruder_filaments.get_selected_filament()->is_compatible : + m_collection->get_selected_idx() != size_t(-1) && !m_collection->get_selected_preset().is_compatible; null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width; std::map nonsys_presets; std::map template_presets; + const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates"); + wxString selected_user_preset; wxString tooltip; const std::deque& presets = m_collection->get_presets(); @@ -848,13 +846,15 @@ void PlaterPresetComboBox::update() for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; - bool is_selected = m_type == Preset::TYPE_FILAMENT ? - m_preset_bundle->filament_presets[m_extruder_idx] == preset.name : + const bool is_selected = m_type == Preset::TYPE_FILAMENT ? + selected_filament_preset->name == preset.name : // The case, when some physical printer is selected m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection() ? false : i == m_collection->get_selected_idx(); - if (!preset.is_visible || (!preset.is_compatible && !is_selected)) + const bool is_compatible = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.filament(i).is_compatible : preset.is_compatible; + + if (!preset.is_visible || (!is_compatible && !is_selected)) continue; std::string bitmap_key, filament_rgb, extruder_rgb, material_rgb; @@ -878,17 +878,19 @@ void PlaterPresetComboBox::update() } auto bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, - preset.is_compatible, preset.is_system || preset.is_default, + is_compatible, preset.is_system || preset.is_default, single_bar, filament_rgb, extruder_rgb, material_rgb); assert(bmp); const std::string name = preset.alias.empty() ? preset.name : preset.alias; if (preset.is_default || preset.is_system) { if (preset.vendor && preset.vendor->templates_profile) { - template_presets.emplace(get_preset_name(preset), bmp); - if (is_selected) { - selected_user_preset = get_preset_name(preset); - tooltip = from_u8(preset.name); + if (allow_templates) { + template_presets.emplace(get_preset_name(preset), bmp); + if (is_selected) { + selected_user_preset = get_preset_name(preset); + tooltip = from_u8(preset.name); + } } } else { Append(get_preset_name(preset), *bmp); @@ -919,8 +921,7 @@ void PlaterPresetComboBox::update() } } - const AppConfig* app_config = wxGetApp().app_config; - if (!template_presets.empty() && app_config->get("no_templates") == "0") { + if (!template_presets.empty()) { set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { Append(it->first, *it->second); @@ -1063,15 +1064,19 @@ void TabPresetComboBox::update() Clear(); invalidate_selection(); + const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_active_extruder_idx]; + const std::deque& presets = m_collection->get_presets(); std::map> nonsys_presets; std::map> template_presets; + const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates"); + wxString selected = ""; if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); - size_t idx_selected = m_collection->get_selected_idx(); + size_t idx_selected = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.get_selected_idx() : m_collection->get_selected_idx(); if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name(); @@ -1083,7 +1088,10 @@ void TabPresetComboBox::update() for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; - if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) + + const bool is_compatible = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.filament(i).is_compatible : preset.is_compatible; + + if (!preset.is_visible || (!show_incompatible && !is_compatible && i != idx_selected)) continue; // marker used for disable incompatible printer models for the selected physical printer @@ -1097,14 +1105,16 @@ void TabPresetComboBox::update() } std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); + auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, is_compatible, preset.is_system || preset.is_default); assert(bmp); - if (preset.is_default || preset.is_system) { + if (preset.is_default || preset.is_system) { if (preset.vendor && preset.vendor->templates_profile) { - template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); - if (i == idx_selected) - selected = get_preset_name(preset); + if (allow_templates) { + template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + if (i == idx_selected) + selected = get_preset_name(preset); + } } else { int item_id = Append(get_preset_name(preset), *bmp); if (!is_enabled) @@ -1136,9 +1146,8 @@ void TabPresetComboBox::update() validate_selection(it->first == selected); } } - - const AppConfig* app_config = wxGetApp().app_config; - if (!template_presets.empty() && app_config->get("no_templates") == "0") { + + if (!template_presets.empty()) { set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { int item_id = Append(it->first, *it->second.first); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 2a477ac11..6575c1a01 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -179,6 +179,8 @@ class TabPresetComboBox : public PresetComboBox { bool show_incompatible {false}; bool m_enable_all {false}; + // This parameter is used by FilamentSettings tab to show filament setting related to the active extruder + int m_active_extruder_idx {0}; public: TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); @@ -197,6 +199,9 @@ public: PresetCollection* presets() const { return m_collection; } Preset::Type type() const { return m_type; } + + // used by Filaments tab to update preset list according to the particular extruder + void set_active_extruder(int extruder_idx) { m_active_extruder_idx = extruder_idx; } }; } // namespace GUI diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 15017ba93..1bc2e5d71 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -75,9 +75,9 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle { // Find out, to which nozzle index is the current filament profile assigned. int idx_extruder = 0; - int num_extruders = (int)preset_bundle.filament_presets.size(); + int num_extruders = (int)preset_bundle.extruders_filaments.size(); for (; idx_extruder < num_extruders; ++ idx_extruder) - if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset_name()) + if (preset_bundle.extruders_filaments[idx_extruder].get_selected_preset_name() == preset_bundle.filaments.get_selected_preset_name()) break; if (idx_extruder == num_extruders) // The current filament preset is not active for any extruder. diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 39363dbf4..cbd61a1dd 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -858,6 +858,43 @@ std::pair Selection::get_bounding_box_in_reference_s return { out_box, out_trafo.get_matrix_no_scaling_factor() }; } +BoundingBoxf Selection::get_screen_space_bounding_box() +{ + BoundingBoxf ss_box; + if (!is_empty()) { + const auto& [box, box_trafo] = get_bounding_box_in_current_reference_system(); + + // vertices + std::vector vertices = { + { box.min.x(), box.min.y(), box.min.z() }, + { box.max.x(), box.min.y(), box.min.z() }, + { box.max.x(), box.max.y(), box.min.z() }, + { box.min.x(), box.max.y(), box.min.z() }, + { box.min.x(), box.min.y(), box.max.z() }, + { box.max.x(), box.min.y(), box.max.z() }, + { box.max.x(), box.max.y(), box.max.z() }, + { box.min.x(), box.max.y(), box.max.z() } + }; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + const std::array& viewport = camera.get_viewport(); + + const double half_w = 0.5 * double(viewport[2]); + const double h = double(viewport[3]); + const double half_h = 0.5 * h; + for (const Vec3d& v : vertices) { + const Vec3d world = box_trafo * v; + const Vec4d clip = projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0); + const Vec3d ndc = Vec3d(clip.x(), clip.y(), clip.z()) / clip.w(); + const Vec2d ss = Vec2d(half_w * ndc.x() + double(viewport[0]) + half_w, h - (half_h * ndc.y() + double(viewport[1]) + half_h)); + ss_box.merge(ss); + } + } + + return ss_box; +} + void Selection::setup_cache() { if (!m_valid) @@ -1717,8 +1754,7 @@ std::vector Selection::get_volume_idxs_from_volume(unsigned int ob { std::vector idxs; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { const GLVolume* v = (*m_volumes)[i]; if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) { if ((int)instance_idx != -1 && v->instance_idx() == (int)instance_idx) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 6395afbd7..5a6fcb5e0 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -319,6 +319,9 @@ public: // and the transform to place and orient it in world coordinates std::pair get_bounding_box_in_reference_system(ECoordinatesType type) const; + // Returns the screen space bounding box + BoundingBoxf get_screen_space_bounding_box(); + void setup_cache(); void translate(const Vec3d& displacement, TransformationType transformation_type); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 2cf4969cb..b8cb3c4c6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1236,7 +1236,8 @@ void Tab::on_presets_changed() m_dependent_tabs.clear(); // Update Project dirty state, update application title bar. - wxGetApp().plater()->update_project_dirty_from_presets(); + if (wxGetApp().mainframe) + wxGetApp().plater()->update_project_dirty_from_presets(); } void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) @@ -1933,8 +1934,64 @@ void TabFilament::update_filament_overrides_page() } } +void TabFilament::create_extruder_combobox() +{ + m_extruders_cb = new BitmapComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(12 * m_em_unit, -1), 0, nullptr, wxCB_READONLY); + m_extruders_cb->Hide(); + + m_extruders_cb->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { + set_active_extruder(m_extruders_cb->GetSelection()); + }); + + m_h_buttons_sizer->AddSpacer(3*em_unit(this)); + m_h_buttons_sizer->Add(m_extruders_cb, 0, wxALIGN_CENTER_VERTICAL); +} + +void TabFilament::update_extruder_combobox() +{ + const size_t extruder_cnt = static_cast(m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); + + m_extruders_cb->Show(extruder_cnt > 1); + + if (extruder_cnt != m_extruders_cb->GetCount()) { + m_extruders_cb->Clear(); + for (size_t id = 1; id <= extruder_cnt; id++) + m_extruders_cb->Append(format_wxstr("%1% %2%", _L("Extruder"), id), *get_bmp_bundle("funnel")); + } + + if (m_active_extruder >= int(extruder_cnt)) + m_active_extruder = 0; + + m_extruders_cb->SetSelection(m_active_extruder); +} + +bool TabFilament::set_active_extruder(int new_selected_extruder) +{ + if (m_active_extruder == new_selected_extruder) + return true; + + const int old_extruder_id = m_active_extruder; + m_active_extruder = new_selected_extruder; + m_presets_choice->set_active_extruder(m_active_extruder); + + if (!select_preset(m_preset_bundle->extruders_filaments[m_active_extruder].get_selected_preset_name())) { + m_active_extruder = old_extruder_id; + m_presets_choice->set_active_extruder(m_active_extruder); + m_extruders_cb->SetSelection(m_active_extruder); + return false; + } + + if (m_active_extruder != m_extruders_cb->GetSelection()) + m_extruders_cb->Select(m_active_extruder); + + return true; +} + void TabFilament::build() { + // add extruder combobox + create_extruder_combobox(); + m_presets = &m_preset_bundle->filaments; load_initial_data(); @@ -2189,7 +2246,7 @@ void TabFilament::update() m_update_cnt--; - if (m_update_cnt == 0) + if (m_update_cnt == 0 && wxGetApp().mainframe) wxGetApp().mainframe->on_config_changed(m_config); } @@ -2210,6 +2267,44 @@ void TabFilament::msw_rescale() Tab::msw_rescale(); } +void TabFilament::load_current_preset() +{ + assert(m_active_extruder >= 0 && m_active_extruder < m_preset_bundle->extruders_filaments.size()); + const std::string& selected_extr_filament_name = m_preset_bundle->extruders_filaments[m_active_extruder].get_selected_preset_name(); + const std::string& selected_filament_name = m_presets->get_selected_preset_name(); + if (selected_extr_filament_name != selected_filament_name) + m_presets->select_preset_by_name(selected_extr_filament_name, false); + + Tab::load_current_preset(); +} + +bool TabFilament::select_preset_by_name(const std::string &name_w_suffix, bool force) +{ + const bool is_selected_filament = Tab::select_preset_by_name(name_w_suffix, force); + const bool is_selected_extr_filament = m_preset_bundle->extruders_filaments[m_active_extruder].select_filament(name_w_suffix, force); + return is_selected_filament && is_selected_extr_filament; +} + +bool TabFilament::save_current_preset(const std::string &new_name, bool detach) +{ + m_preset_bundle->cache_extruder_filaments_names(); + const bool is_saved = Tab::save_current_preset(new_name, detach); + if (is_saved) { + m_preset_bundle->reset_extruder_filaments(); + m_preset_bundle->extruders_filaments[m_active_extruder].select_filament(m_presets->get_idx_selected()); + } + return is_saved; +} + +bool TabFilament::delete_current_preset() +{ + m_preset_bundle->cache_extruder_filaments_names(); + const bool is_deleted = Tab::delete_current_preset(); + if (is_deleted) + m_preset_bundle->reset_extruder_filaments(); + return is_deleted; +} + wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText, wxString text /*= wxEmptyString*/) { *StaticText = new ogStaticText(parent, text); @@ -2339,7 +2434,6 @@ void TabPrinter::build_fff() const wxString msg_text = _(L("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?")); - //wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); MessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); DynamicPrintConfig new_conf = *m_config; @@ -2357,6 +2451,14 @@ void TabPrinter::build_fff() } } } + + m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); + // Upadte related comboboxes on Sidebar and Tabs + Sidebar& sidebar = wxGetApp().plater()->sidebar(); + for (const Preset::Type& type : {Preset::TYPE_PRINT, Preset::TYPE_FILAMENT}) { + sidebar.update_presets(type); + wxGetApp().get_tab(type)->update_tab_ui(); + } } } else { @@ -2625,15 +2727,21 @@ void TabPrinter::build_sla() void TabPrinter::extruders_count_changed(size_t extruders_count) { bool is_count_changed = false; + bool is_updated_mm_filament_presets = false; if (m_extruders_count != extruders_count) { m_extruders_count = extruders_count; m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count); - m_preset_bundle->update_multi_material_filament_presets(); - is_count_changed = true; + is_count_changed = is_updated_mm_filament_presets = true; } else if (m_extruders_count == 1 && - m_preset_bundle->project_config.option("wiping_volumes_matrix")->values.size()>1) + m_preset_bundle->project_config.option("wiping_volumes_matrix")->values.size()>1) { + is_updated_mm_filament_presets = true; + } + + if (is_updated_mm_filament_presets) { m_preset_bundle->update_multi_material_filament_presets(); + m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::OnlyIfWasCompatible); + } /* This function should be call in any case because of correct updating/rebuilding * of unregular pages of a Printer Settings @@ -2768,7 +2876,10 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders) optgroup->m_on_change = [this, extruder_idx](const t_config_option_key&opt_key, boost::any value) { - if (m_config->opt_bool("single_extruder_multi_material") && m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos) + const bool is_single_extruder_MM = m_config->opt_bool("single_extruder_multi_material"); + const bool is_nozzle_diameter_changed = opt_key.find_first_of("nozzle_diameter") != std::string::npos; + + if (is_single_extruder_MM && m_extruders_count > 1 && is_nozzle_diameter_changed) { SuppressBackgroundProcessingUpdate sbpu; const double new_nd = boost::any_cast(value); @@ -2798,6 +2909,15 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders) } } + if (is_nozzle_diameter_changed) { + if (extruder_idx == 0) + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. + m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); + else + m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::Never, extruder_idx); + } + update_dirty(); update(); }; @@ -3040,6 +3160,7 @@ void TabPrinter::update_pages() if (m_extruders_count > 1) { m_preset_bundle->update_multi_material_filament_presets(); + m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::OnlyIfWasCompatible); on_value_change("extruders_count", m_extruders_count); } } @@ -3392,7 +3513,7 @@ void Tab::update_preset_choice() // Called by the UI combo box when the user switches profiles, and also to delete the current profile. // Select a preset by a name.If !defined(name), then the default preset is selected. // If the current profile is modified, user is asked to save the changes. -void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const std::string& last_selected_ph_printer_name/* =""*/) +bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const std::string& last_selected_ph_printer_name/* =""*/) { if (preset_name.empty()) { if (delete_current) { @@ -3492,7 +3613,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, // It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable. // The 'external' presets will only be removed from the preset list, their files will not be deleted. try { - m_presets->delete_current_preset(); + // cache previously selected names + delete_current_preset(); } catch (const std::exception & /* e */) { //FIXME add some error reporting! canceled = true; @@ -3513,7 +3635,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, } } - update_tab_ui(); + // update_tab_ui(); //! ysFIXME delete after testing // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the plater. @@ -3522,7 +3644,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (current_dirty) m_presets->discard_current_changes(); - const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current; + const bool is_selected = select_preset_by_name(preset_name, false) || delete_current; assert(m_presets->get_edited_preset().name == preset_name || ! is_selected); // Mark the print & filament enabled if they are compatible with the currently selected preset. // The following method should not discard changes of current print or filament presets on change of a printer profile, @@ -3565,6 +3687,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (technology_changed) wxGetApp().mainframe->technology_changed(); + + return !canceled; } // If the current preset is dirty, the user is asked whether the changes may be discarded. @@ -3812,16 +3936,12 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // focus currently.is there anything better than this ? //! m_treectrl->OnSetFocus(); - auto& old_preset = m_presets->get_edited_preset(); + Preset& edited_preset = m_presets->get_edited_preset(); bool from_template = false; std::string edited_printer; - if (m_type == Preset::TYPE_FILAMENT && old_preset.vendor && old_preset.vendor->templates_profile) - { - //TODO: is this really the best way to get "printer_model" option of currently edited printer? - edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt("printer_model")->serialize(); - if (!edited_printer.empty()) - from_template = true; - + if (m_type == Preset::TYPE_FILAMENT && edited_preset.vendor && edited_preset.vendor->templates_profile) { + edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_model"); + from_template = !edited_printer.empty(); } if (name.empty()) { @@ -3836,24 +3956,21 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) if (detach && m_type == Preset::TYPE_PRINTER) m_config->opt_string("printer_model", true) = ""; + // Update compatible printers + if (from_template && !edited_printer.empty()) { + std::string cond = edited_preset.compatible_printers_condition(); + if (!cond.empty()) + cond += " and "; + cond += "printer_model == \"" + edited_printer + "\""; + edited_preset.config.opt_string("compatible_printers_condition") = cond; + } + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini - m_presets->save_current_preset(name, detach); + save_current_preset(name, detach); if (detach && m_type == Preset::TYPE_PRINTER) wxGetApp().mainframe->on_config_changed(m_config); - // Update compatible printers - if (from_template && !edited_printer.empty()) { - auto& new_preset = m_presets->get_edited_preset(); - std::string cond = new_preset.compatible_printers_condition(); - if (!cond.empty()) - cond += " and "; - cond += "printer_model == \""+edited_printer+"\""; - new_preset.config.set("compatible_printers_condition", cond); - new_preset.save(); - m_presets->save_current_preset(name, detach); - load_current_preset(); - } // Mark the print & filament enabled if they are compatible with the currently selected preset. // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. @@ -3980,7 +4097,7 @@ void Tab::rename_preset() // sort presets after renaming std::sort(m_presets->begin(), m_presets->end()); // update selection - m_presets->select_preset_by_name(new_name, true); + select_preset_by_name(new_name, true); m_presets_choice->update(); on_presets_changed(); @@ -4675,6 +4792,21 @@ void Tab::set_tooltips_text() "Click to reset current value to the last saved preset.")); } +bool Tab::select_preset_by_name(const std::string &name_w_suffix, bool force) +{ + return m_presets->select_preset_by_name(name_w_suffix, force); +} + +bool Tab::save_current_preset(const std::string& new_name, bool detach) +{ + return m_presets->save_current_preset(new_name, detach); +} + +bool Tab::delete_current_preset() +{ + return m_presets->delete_current_preset(); +} + Page::Page(wxWindow* parent, const wxString& title, int iconID) : m_parent(parent), m_title(title), diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 1a2edfe05..bbe1d1b17 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -307,12 +307,13 @@ public: long style = wxBU_EXACTFIT | wxNO_BORDER); void add_scaled_bitmap(wxWindow* parent, ScalableBitmap& btn, const std::string& icon_name); void update_ui_items_related_on_parent_preset(const Preset* selected_preset_parent); - void load_current_preset(); + virtual void load_current_preset(); void rebuild_page_tree(); void update_btns_enabling(); void update_preset_choice(); // Select a new preset, possibly delete the current one. - void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); + // return false, if action was canceled + bool select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); virtual void clear_pages(); @@ -402,6 +403,10 @@ protected: void fill_icon_descriptions(); void set_tooltips_text(); + virtual bool select_preset_by_name(const std::string& name_w_suffix, bool force); + virtual bool save_current_preset(const std::string& new_name, bool detach); + virtual bool delete_current_preset(); + ConfigManipulation m_config_manipulation; ConfigManipulation get_config_manipulation(); }; @@ -432,7 +437,8 @@ private: class TabFilament : public Tab { -private: + BitmapComboBox* m_extruders_cb {nullptr}; + int m_active_extruder {0}; ogStaticText* m_volumetric_speed_description_line {nullptr}; ogStaticText* m_cooling_description_line {nullptr}; @@ -440,6 +446,7 @@ private: void update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0, bool is_checked = true); void add_filament_overrides_page(); void update_filament_overrides_page(); + void create_extruder_combobox(); void update_volumetric_flow_preset_hints(); std::map m_overrides_options; @@ -455,6 +462,18 @@ public: void clear_pages() override; void msw_rescale() override; bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } + void load_current_preset() override; + + // set actiev extruder and update preset combobox if needed + // return false, if new preset wasn't selected + bool set_active_extruder(int new_selected_extruder); + void update_extruder_combobox(); + int get_active_extruder() const { return m_active_extruder; } + +protected: + bool select_preset_by_name(const std::string& name_w_suffix, bool force) override; + bool save_current_preset(const std::string& new_name, bool detach) override; + bool delete_current_preset() override; }; class TabPrinter : public Tab diff --git a/tests/fff_print/test_cooling.cpp b/tests/fff_print/test_cooling.cpp index 361a4c311..778a7da40 100644 --- a/tests/fff_print/test_cooling.cpp +++ b/tests/fff_print/test_cooling.cpp @@ -260,7 +260,7 @@ SCENARIO("Cooling integration tests", "[Cooling]") { }); THEN("slowdown_below_layer_time is honored") { // Account for some inaccuracies. - const double slowdown_below_layer_time = config.opt("slowdown_below_layer_time")->values.front() - 0.2; + const double slowdown_below_layer_time = config.opt("slowdown_below_layer_time")->values.front() - 0.5; size_t minimum_time_honored = std::count_if(layer_times.begin(), layer_times.end(), [slowdown_below_layer_time](double t){ return t > slowdown_below_layer_time; }); REQUIRE(minimum_time_honored == layer_times.size()); diff --git a/tests/fff_print/test_shells.cpp b/tests/fff_print/test_shells.cpp index f5b919786..fcb3a49f2 100644 --- a/tests/fff_print/test_shells.cpp +++ b/tests/fff_print/test_shells.cpp @@ -48,9 +48,11 @@ SCENARIO("Shells", "[Shells]") { REQUIRE(! has_shells(i)); } THEN("correct number of top solid layers") { - for (int i = 0; i < top_solid_layers; ++ i) + // NOTE: there is one additional layer with enusring line under the bridge layer, bridges would be otherwise anchored weakly to the perimeter. + size_t additional_ensuring_anchors = top_solid_layers > 0 ? 1 : 0; + for (int i = 0; i < top_solid_layers + additional_ensuring_anchors; ++ i) REQUIRE(has_shells(int(zs.size()) - i - 1)); - for (int i = top_solid_layers; i < int(zs.size() / 2); ++ i) + for (int i = top_solid_layers + additional_ensuring_anchors; i < int(zs.size() / 2); ++ i) REQUIRE(! has_shells(int(zs.size()) - i - 1)); } if (top_solid_layers > 0) { @@ -144,7 +146,7 @@ SCENARIO("Shells (from Perl)", "[Shells]") { for (auto z : layers_with_speed(Slic3r::Test::slice({TestMesh::V}, config), solid_speed)) if (z <= 7.2) ++ n; - REQUIRE(n == 3); + REQUIRE(n == 3 + 1/*one additional layer with ensuring for bridge anchors*/); } }