diff --git a/resources/shapes/M3_hex_nut.png b/resources/shapes/M3_hex_nut.png new file mode 100644 index 000000000..26dbb634c Binary files /dev/null and b/resources/shapes/M3_hex_nut.png differ diff --git a/resources/shapes/M3_hex_nut.stl b/resources/shapes/M3_hex_nut.stl new file mode 100644 index 000000000..2be831705 Binary files /dev/null and b/resources/shapes/M3_hex_nut.stl differ diff --git a/resources/shapes/M3x10_screw.png b/resources/shapes/M3x10_screw.png new file mode 100644 index 000000000..623efd460 Binary files /dev/null and b/resources/shapes/M3x10_screw.png differ diff --git a/resources/shapes/M3x10_screw.stl b/resources/shapes/M3x10_screw.stl new file mode 100644 index 000000000..e6d37185e Binary files /dev/null and b/resources/shapes/M3x10_screw.stl differ diff --git a/resources/shapes/cone.png b/resources/shapes/cone.png new file mode 100644 index 000000000..87bc51d46 Binary files /dev/null and b/resources/shapes/cone.png differ diff --git a/resources/shapes/cone.stl b/resources/shapes/cone.stl new file mode 100644 index 000000000..f75c6d772 Binary files /dev/null and b/resources/shapes/cone.stl differ diff --git a/resources/shapes/helper_disk.png b/resources/shapes/helper_disk.png new file mode 100644 index 000000000..602f33cbf Binary files /dev/null and b/resources/shapes/helper_disk.png differ diff --git a/resources/shapes/helper_disk.stl b/resources/shapes/helper_disk.stl new file mode 100644 index 000000000..94ab0739c Binary files /dev/null and b/resources/shapes/helper_disk.stl differ diff --git a/resources/shapes/torus.png b/resources/shapes/torus.png new file mode 100644 index 000000000..16e8bac5a Binary files /dev/null and b/resources/shapes/torus.png differ diff --git a/resources/shapes/torus.stl b/resources/shapes/torus.stl new file mode 100644 index 000000000..997fd44b5 Binary files /dev/null and b/resources/shapes/torus.stl differ diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index fd2c4f865..d5444edc6 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -121,6 +121,9 @@ void AppConfig::set_defaults() if (get("auto_toolbar_size").empty()) set("auto_toolbar_size", "100"); + + if (get("notify_release").empty()) + set("notify_release", "all"); // or "none" or "release" #if ENABLE_ENVIRONMENT_MAP if (get("use_environment_map").empty()) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 45c0de616..225a86f37 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2679,7 +2679,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); // check for seam ending vertex and store the resulting move - else if ((type != EMoveType::Extrude || m_extrusion_role != erExternalPerimeter) && m_seams_detector.has_first_vertex()) { + else if ((type != EMoveType::Extrude || (m_extrusion_role != erExternalPerimeter && m_extrusion_role != erOverhangPerimeter)) && m_seams_detector.has_first_vertex()) { auto set_end_position = [this](const Vec3f& pos) { m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); }; @@ -2688,6 +2688,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; const std::optional first_vertex = m_seams_detector.get_first_vertex(); // the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later + if ((new_pos - *first_vertex).squaredNorm() < 0.0625f) { set_end_position(0.5f * (new_pos + *first_vertex)); store_move_vertex(EMoveType::Seam); @@ -2697,6 +2698,10 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_seams_detector.activate(false); } } + else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { + m_seams_detector.activate(true); + m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); + } // store move store_move_vertex(type); diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index e60918fab..ab99ea5f6 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -47,7 +47,7 @@ private: public: // Forwarded constructor template - inline CachedObject(Setter fn, Args &&... args) + inline CachedObject(Setter &&fn, Args &&... args) : m_obj(std::forward(args)...), m_valid(false), m_setter(fn) {} @@ -55,7 +55,7 @@ public: // the next retrieval (Setter will be called). The data that is used in // the setter function should be guarded as well during modification so // the modification has to take place in fn. - inline void invalidate(std::function fn) + template void invalidate(Fn &&fn) { std::lock_guard lck(m_lck); fn(); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index b48c71828..a91d43e78 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -190,8 +190,7 @@ static inline std::vector to_lines(const std::vector; ulp_cmp_type ulp_cmp; static constexpr int ULPS = boost::polygon::voronoi_diagram_traits::vertex_equality_predicate_type::ULPS; - return ulp_cmp(vertex.x(), double(ipt.x()), ULPS) == ulp_cmp_type::EQUAL && - ulp_cmp(vertex.y(), double(ipt.y()), ULPS) == ulp_cmp_type::EQUAL; + return ulp_cmp(vertex.x(), ipt.x(), ULPS) == ulp_cmp_type::EQUAL && + ulp_cmp(vertex.y(), ipt.y(), ULPS) == ulp_cmp_type::EQUAL; } -static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Point &ipt) +static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Vec2d &ipt) { return vertex_equal_to_point(*vertex, ipt); } @@ -509,6 +508,8 @@ static inline Point mk_point(const Voronoi::Internal::point_type &point) { retur static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } +static inline Point mk_point(const Vec2d &point) { return {coord_t(std::round(point.x())), coord_t(std::round(point.y()))}; } + static inline Vec2d mk_vec2(const voronoi_diagram::vertex_type *point) { return {point->x(), point->y()}; } struct MMU_Graph @@ -528,7 +529,7 @@ struct MMU_Graph struct Node { - Point point; + Vec2d point; std::list arc_idxs; void remove_edge(const size_t to_idx, MMU_Graph &graph) @@ -665,48 +666,67 @@ struct MMU_Graph struct CPoint { CPoint() = delete; - CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {} - CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {} + CPoint(const Vec2d &point, size_t contour_idx, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(contour_idx) {} + CPoint(const Vec2d &point, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(0) {} + const Vec2d m_point_double; const Point m_point; size_t m_point_idx; size_t m_contour_idx; + [[nodiscard]] const Vec2d &point_double() const { return m_point_double; } [[nodiscard]] const Point &point() const { return m_point; } - bool operator==(const CPoint &rhs) const { return this->m_point == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; } + bool operator==(const CPoint &rhs) const { return this->m_point_double == rhs.m_point_double && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; } }; struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }}; typedef ClosestPointInRadiusLookup CPointLookupType; - CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON)); + CPointLookupType closest_voronoi_point(coord_t(SCALED_EPSILON)); CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON)); for (const Polygon &polygon : color_poly_tmp) for (const Point &pt : polygon.points) - closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front())); + closest_contour_point.insert(CPoint(Vec2d(pt.x(), pt.y()), &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front())); for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { vertex.color(-1); - Point vertex_point = mk_point(vertex); + Vec2d vertex_point_double = Vec2d(vertex.x(), vertex.y()); + Point vertex_point = mk_point(vertex); - const Point &first_point = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; - const Point &second_point = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; + const Vec2d &first_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; + const Vec2d &second_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; - if (vertex_equal_to_point(&vertex, first_point)) { + if (vertex_equal_to_point(&vertex, first_point_double)) { assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); vertex.color(this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx); - } else if (vertex_equal_to_point(&vertex, second_point)) { + } else if (vertex_equal_to_point(&vertex, second_point_double)) { assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); } else if (bbox.contains(vertex_point)) { if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) { vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx)); - } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(3 * SCALED_EPSILON)) { - closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count())); + } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(SCALED_EPSILON / 10.0)) { + closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count())); vertex.color(this->nodes_count()); - this->nodes.push_back({vertex_point}); + this->nodes.push_back({vertex_point_double}); } else { - vertex.color(voronoi_pt->m_point_idx); + // Boost Voronoi diagram generator sometimes creates two very closed points instead of one point. + // For the example points (146872.99999999997, -146872.99999999997) and (146873, -146873), this example also included in Voronoi generator test cases. + std::vector> all_closes_c_points = closest_voronoi_point.find_all(vertex_point); + int merge_to_point = -1; + for (const std::pair &c_point : all_closes_c_points) + if ((vertex_point_double - c_point.first->point_double()).squaredNorm() <= Slic3r::sqr(EPSILON)) { + merge_to_point = int(c_point.first->m_point_idx); + break; + } + + if (merge_to_point != -1) { + vertex.color(merge_to_point); + } else { + closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count())); + vertex.color(this->nodes_count()); + this->nodes.push_back({vertex_point_double}); + } } } } @@ -850,7 +870,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vectorvertex0()->color()].point; - Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point; + Vec2d real_v0_double = graph.nodes[edge_it->vertex0()->color()].point; + Vec2d real_v1_double = graph.nodes[edge_it->vertex1()->color()].point; + Point real_v0 = Point(coord_t(real_v0_double.x()), coord_t(real_v0_double.y())); + Point real_v1 = Point(coord_t(real_v1_double.x()), coord_t(real_v1_double.y())); if (is_point_closer_to_beginning_of_line(contour_line, intersection)) { Line first_part(intersection, real_v0); @@ -999,8 +1021,9 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vectorvertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx); } } else { - const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx; - const Point int_point = graph.nodes[int_point_idx].point; + const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx; + const Vec2d int_point_double = graph.nodes[int_point_idx].point; + const Point int_point = Point(coord_t(int_point_double.x()), coord_t(int_point_double.y())); const Line first_part(int_point, real_v0); const Line second_part(int_point, real_v1); @@ -1039,12 +1062,12 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector &lines) { Polygon poly_out; poly_out.points.reserve(lines.size()); - for (const Line &line : lines) - poly_out.points.emplace_back(line.a); + for (const Linef &line : lines) + poly_out.points.emplace_back(mk_point(line.a)); return poly_out; } @@ -1056,7 +1079,7 @@ static std::vector> extract_colored_segments(const MM { std::vector used_arcs(graph.arcs.size(), false); // When there is no next arc, then is returned original_arc or edge with is marked as used - auto get_next = [&graph, &used_arcs](const Line &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & { + auto get_next = [&graph, &used_arcs](const Linef &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & { std::vector> sorted_arcs; for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) { const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; @@ -1064,8 +1087,8 @@ static std::vector> extract_colored_segments(const MM continue; assert(original_arc.to_idx == arc.from_idx); - Vec2d process_line_vec_n = (process_line.a - process_line.b).cast().normalized(); - Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).cast().normalized(); + Vec2d process_line_vec_n = (process_line.a - process_line.b).normalized(); + Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).normalized(); double angle = ::acos(std::clamp(neighbour_line_vec_n.dot(process_line_vec_n), -1.0, 1.0)); if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0) @@ -1098,17 +1121,17 @@ static std::vector> extract_colored_segments(const MM for (const size_t &arc_idx : node.arc_idxs) { const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx])continue; + if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx]) + continue; - - Line process_line(node.point, graph.nodes[arc.to_idx].point); + Linef process_line(node.point, graph.nodes[arc.to_idx].point); used_arcs[arc_idx] = true; - Lines face_lines; + std::vector face_lines; face_lines.emplace_back(process_line); - Point start_p = process_line.a; + Vec2d start_p = process_line.a; - Line p_vec = process_line; + Linef p_vec = process_line; const MMU_Graph::Arc *p_arc = &arc; do { const MMU_Graph::Arc &next = get_next(p_vec, *p_arc); @@ -1118,7 +1141,7 @@ static std::vector> extract_colored_segments(const MM break; used_arcs[next_arc_idx] = true; - p_vec = Line(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); + p_vec = Linef(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); p_arc = &next; } while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx])); @@ -1141,16 +1164,16 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st used_arcs[start_arc_idx] = true; const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx]; size_t idx = start_idx; - double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast().norm();; + double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();; while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) { bool found = false; for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) { if (const MMU_Graph::Arc &arc_n = graph.arcs[arc_idx]; arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !used_arcs[arc_idx] && arc_n.to_idx != idx) { - Line first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point); - Line second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point); + Linef first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point); + Linef second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point); - Vec2d first_line_vec = (first_line.a - first_line.b).cast(); - Vec2d second_line_vec = (second_line.b - second_line.a).cast(); + Vec2d first_line_vec = (first_line.a - first_line.b); + Vec2d second_line_vec = (second_line.b - second_line.a); Vec2d first_line_vec_n = first_line_vec.normalized(); Vec2d second_line_vec_n = second_line_vec.normalized(); double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0)); @@ -1163,7 +1186,7 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st idx = arc->to_idx; arc = &arc_n; - line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast().norm(); + line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm(); used_arcs[arc_idx] = true; found = true; break; @@ -1185,7 +1208,7 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto for (const std::pair &colored_segment : colored_segment_p) { size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first); size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]); - Line seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point); + Linef seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point); if (graph.nodes[first_idx].arc_idxs.size() >= 3) { std::vector> arc_to_check; @@ -1502,7 +1525,7 @@ static void export_graph_to_svg(const std::string &path, const MMU_Graph &graph, for (const MMU_Graph::Node &node : graph.nodes) for (const size_t &arc_idx : node.arc_idxs) { const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - Line arc_line(node.point, graph.nodes[arc.to_idx].point); + Line arc_line(mk_point(node.point), mk_point(graph.nodes[arc.to_idx].point)); if (arc.type == MMU_Graph::ARC_TYPE::BORDER && arc.color >= 0 && arc.color < int(colors.size())) svg.draw(arc_line, colors[arc.color], stroke_width); else diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 004f7d555..1bc548914 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -138,14 +138,14 @@ Transform3d SLAPrint::sla_trafo(const ModelObject &model_object) const offset(1) = 0.; rotation(2) = 0.; - offset(Z) *= corr(Z); + offset.z() *= corr.z(); auto trafo = Transform3d::Identity(); trafo.translate(offset); trafo.scale(corr); - trafo.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); - trafo.rotate(Eigen::AngleAxisd(rotation(1), Vec3d::UnitY())); - trafo.rotate(Eigen::AngleAxisd(rotation(0), Vec3d::UnitX())); + trafo.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ())); + trafo.rotate(Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY())); + trafo.rotate(Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX())); trafo.scale(model_instance.get_scaling_factor()); trafo.scale(model_instance.get_mirror()); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 2099cbf73..9a5638c01 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -411,6 +411,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; + // Per object layer projection of the object below the layer into print bed. std::vector buildplate_covered = this->buildplate_covered(object); // Determine the top contact surfaces of the support, defined as: @@ -1241,7 +1242,7 @@ namespace SupportMaterialInternal { const PrintConfig &print_config, const Layer &lower_layer, const Polygons &lower_layer_polygons, - LayerRegion *layerm, + const LayerRegion &layerm, float fw, Polygons &contact_polygons) { @@ -1251,19 +1252,19 @@ namespace SupportMaterialInternal { // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. Polygons lower_grown_slices = offset(lower_layer_polygons, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region().config().perimeter_extruder-1))), + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); // Collect perimeters of this layer. //FIXME split_at_first_point() could split a bridge mid-way #if 0 - Polylines overhang_perimeters = layerm->perimeters.as_polylines(); + Polylines overhang_perimeters = layerm.perimeters.as_polylines(); // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() for (Polyline &polyline : overhang_perimeters) polyline.points[0].x += 1; // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); #else - Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices); + Polylines overhang_perimeters = diff_pl(layerm.perimeters.as_polylines(), lower_grown_slices); #endif // only consider straight overhangs @@ -1272,7 +1273,7 @@ namespace SupportMaterialInternal { // since we're dealing with bridges, we can't assume width is larger than spacing, // so we take the largest value and also apply safety offset to be ensure no gaps // are left in between - Flow perimeter_bridge_flow = layerm->bridging_flow(frPerimeter); + Flow perimeter_bridge_flow = layerm.bridging_flow(frPerimeter); float w = float(std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())); for (Polyline &polyline : overhang_perimeters) if (polyline.is_straight()) { @@ -1293,8 +1294,8 @@ namespace SupportMaterialInternal { bridges = union_(bridges); } // remove the entire bridges and only support the unsupported edges - //FIXME the brided regions are already collected as layerm->bridged. Use it? - for (const Surface &surface : layerm->fill_surfaces.surfaces) + //FIXME the brided regions are already collected as layerm.bridged. Use it? + for (const Surface &surface : layerm.fill_surfaces.surfaces) if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) polygons_append(bridges, surface.expolygon); //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? @@ -1302,14 +1303,14 @@ namespace SupportMaterialInternal { //FIXME add supports at regular intervals to support long bridges! bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Remove bridged areas from the supported areas. contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); #ifdef SLIC3R_DEBUG static int iRun = 0; SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), - { { { union_ex(offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, + { { { union_ex(offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ @@ -1325,6 +1326,7 @@ std::vector PrintObjectSupportMaterial::buildplate_covered(const Print if (buildplate_only) { BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - start"; buildplate_covered.assign(object.layers().size(), Polygons()); + //FIXME prefix sum algorithm, parallelize it! Parallelization will also likely be more numerically stable. for (size_t layer_id = 1; layer_id < object.layers().size(); ++ layer_id) { const Layer &lower_layer = *object.layers()[layer_id-1]; // Merge the new slices with the preceding slices. @@ -1368,6 +1370,8 @@ struct SlicesMarginCache Polygons all_polygons; }; +// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset +// no_interface_offset: minimum of external perimeter widths static inline std::tuple detect_overhangs( const Layer &layer, const size_t layer_id, @@ -1412,7 +1416,7 @@ static inline std::tuple detect_overhangs( // Expand for better stability. contact_polygons = offset(overhang_polygons, scaled(object_config.raft_expansion.value)); } - else + else if (! layer.regions().empty()) { // Generate overhang / contact_polygons for non-raft layers. const Layer &lower_layer = *layer.lower_layer; @@ -1426,6 +1430,7 @@ static inline std::tuple detect_overhangs( slices_margin.offset = slices_margin_offset; slices_margin.polygons = (slices_margin_offset == 0.f) ? lower_layer_polygons : + // What is the purpose of no_interface_offset? Likely to not trim the contact layer by lower layer regions that are too thin to extrude? offset2(lower_layer.lslices, -no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); if (buildplate_only && !annotations.buildplate_covered[layer_id].empty()) { if (has_enforcer) @@ -1437,14 +1442,14 @@ static inline std::tuple detect_overhangs( } }; - float fw = 0; + no_interface_offset = std::accumulate(layer.regions().begin(), layer.regions().end(), FLT_MAX, + [](float acc, const LayerRegion *layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); + float lower_layer_offset = 0; - float no_interface_offset = 0; for (LayerRegion *layerm : layer.regions()) { // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. - fw = float(layerm->flow(frExternalPerimeter).scaled_width()); - no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); + float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); lower_layer_offset = (layer_id < (size_t)object_config.support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. @@ -1465,37 +1470,40 @@ static inline std::tuple detect_overhangs( // This step is done before the contact surface is calculated by growing the overhang region. diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); } - } else { - if (support_auto) { - // Get the regions needing a suport, collapse very tiny spots. - //FIXME cache the lower layer offset if this layer has multiple regions. -#if 1 - //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 - diff_polygons = offset2( - diff(layerm_polygons, - offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to - // no support at all for not so steep overhangs. - - 0.1f * fw, 0.1f * fw); + } else if (support_auto) { + // Get the regions needing a suport, collapse very tiny spots. + //FIXME cache the lower layer offset if this layer has multiple regions. +#if 0 + //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 + diff_polygons = offset2( + diff(layerm_polygons, + // Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they + // are not supporting this layer. + // However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports. + // For example, see GH issue #3094 + offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to + // no support at all for not so steep overhangs. + - 0.1f * fw, 0.1f * fw); #else - diff_polygons = - diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + diff_polygons = + diff(layerm_polygons, + offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #endif - if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); - } - if (! diff_polygons.empty()) { - // Offset the support regions back to a full overhang, restrict them to the full overhang. - // This is done to increase size of the supporting columns below, as they are calculated by - // propagating these contact surfaces downwards. - diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); - } + if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calculated by growing the overhang region. + diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); } + if (! diff_polygons.empty()) { + // Offset the support regions back to a full overhang, restrict them to the full overhang. + // This is done to increase size of the supporting columns below, as they are calculated by + // propagating these contact surfaces downwards. + diff_polygons = diff( + intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); + } + //FIXME add user defined filtering here based on minimal area or minimum radius or whatever. } if (diff_polygons.empty()) @@ -1523,8 +1531,9 @@ static inline std::tuple detect_overhangs( #endif /* SLIC3R_DEBUG */ if (object_config.dont_support_bridges) + //FIXME Expensive, potentially not precise enough. Misses gap fill extrusions, which bridge. SupportMaterialInternal::remove_bridges_from_contacts( - print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); + print_config, lower_layer, lower_layer_polygons, *layerm, fw, diff_polygons); if (diff_polygons.empty()) continue; @@ -1579,7 +1588,7 @@ static inline std::tuple detect_overhangs( #endif // SLIC3R_DEBUG enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]), // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. - offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, @@ -1596,6 +1605,8 @@ static inline std::tuple detect_overhangs( return std::make_tuple(std::move(overhang_polygons), std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); } +// Allocate one, possibly two support contact layers. +// For "thick" overhangs, one support layer will be generated to support normal extrusions, the other to support the "thick" extrusions. static inline std::pair new_contact_layer( const PrintConfig &print_config, const PrintObjectConfig &object_config, @@ -1708,6 +1719,7 @@ static inline void fill_contact_layer( auto lower_layer_polygons_for_dense_interface = [&lower_layer_polygons_for_dense_interface_cache, &lower_layer_polygons, no_interface_offset]() -> const Polygons& { if (lower_layer_polygons_for_dense_interface_cache.empty()) lower_layer_polygons_for_dense_interface_cache = + //FIXME no_interface_offset * 0.6f offset is not quite correct, one shall derive it based on an angle thus depending on layer height. offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); return lower_layer_polygons_for_dense_interface_cache; }; @@ -1721,14 +1733,8 @@ static inline void fill_contact_layer( #endif // SLIC3R_DEBUG )); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - if (layer_id == 0 || slicing_params.soluble_interface) { - // if (no_interface_offset == 0.f) { - new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true -#ifdef SLIC3R_DEBUG - , "top_contact_polygons2", iRun, layer_id, layer.print_z -#endif // SLIC3R_DEBUG - ); - } else { + bool reduce_interfaces = layer_id > 0 && ! slicing_params.soluble_interface; + if (reduce_interfaces) { // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); if (! dense_interface_polygons.empty()) { @@ -1746,7 +1752,7 @@ static inline void fill_contact_layer( SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.polygons, grid_params); new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false #ifdef SLIC3R_DEBUG - , "top_contact_polygons3", iRun, layer_id, layer.print_z + , "top_contact_polygons2", iRun, layer_id, layer.print_z #endif // SLIC3R_DEBUG ); #ifdef SLIC3R_DEBUG @@ -1765,45 +1771,59 @@ static inline void fill_contact_layer( { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ } + } else { + new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true +#ifdef SLIC3R_DEBUG + , "top_contact_polygons3", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); } if (! enforcer_polygons.empty() && ! slices_margin.all_polygons.empty() && layer_id > 0) { // Support enforcers used together with support enforcers. The support enforcers need to be handled separately from the rest of the support. - { - SupportGridPattern support_grid_pattern(&enforcer_polygons, &slices_margin.all_polygons, grid_params); - // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - new_layer.enforcer_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true - #ifdef SLIC3R_DEBUG - , "top_contact_polygons4", iRun, layer_id, layer.print_z - #endif // SLIC3R_DEBUG - )); - } - // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. - Polygons dense_interface_polygons = diff(enforcer_polygons, lower_layer_polygons_for_dense_interface()); - if (! dense_interface_polygons.empty()) { - dense_interface_polygons = - diff( - // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), - slices_margin.all_polygons); - // Support islands, to be stretched into a grid. - //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, - // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. - // See for example GH #4874. - Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.enforcer_polygons); - SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.all_polygons, grid_params); - // Extend the polygons to extrude with the contact polygons of support enforcers. - bool needs_union = ! new_layer.polygons.empty(); - append(new_layer.polygons, support_grid_pattern.extract_support(grid_params.expansion_to_slice, false + SupportGridPattern support_grid_pattern(&enforcer_polygons, &slices_margin.all_polygons, grid_params); + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + new_layer.enforcer_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true #ifdef SLIC3R_DEBUG - , "top_contact_polygons5", iRun, layer_id, layer.print_z + , "top_contact_polygons4", iRun, layer_id, layer.print_z #endif // SLIC3R_DEBUG - )); - if (needs_union) - new_layer.polygons = union_(new_layer.polygons); + )); + Polygons new_polygons; + bool needs_union = ! new_layer.polygons.empty(); + if (reduce_interfaces) { + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. + Polygons dense_interface_polygons = diff(enforcer_polygons, lower_layer_polygons_for_dense_interface()); + if (! dense_interface_polygons.empty()) { + dense_interface_polygons = + diff( + // Regularize the contour. + offset(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin.all_polygons); + // Support islands, to be stretched into a grid. + //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, + // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. + // See for example GH #4874. + Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.enforcer_polygons); + SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.all_polygons, grid_params); + // Extend the polygons to extrude with the contact polygons of support enforcers. + new_polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false + #ifdef SLIC3R_DEBUG + , "top_contact_polygons5", iRun, layer_id, layer.print_z + #endif // SLIC3R_DEBUG + ); + } + } else { + new_polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true + #ifdef SLIC3R_DEBUG + , "top_contact_polygons6", iRun, layer_id, layer.print_z + #endif // SLIC3R_DEBUG + ); } + append(new_layer.polygons, std::move(new_polygons)); + if (needs_union) + new_layer.polygons = union_(new_layer.polygons); } #ifdef SLIC3R_DEBUG @@ -1918,8 +1938,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Now apply the contact areas to the layer where they need to be made. if (! contact_polygons.empty()) { + // Allocate the two empty layers. auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); if (new_layer) { + // Fill the non-bridging layer with polygons. fill_contact_layer(*new_layer, layer_id, m_slicing_params, *m_object_config, slices_margin, overhang_polygons, contact_polygons, enforcer_polygons, lower_layer_polygons, m_support_params.support_material_flow, no_interface_offset @@ -1927,6 +1949,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ , iRun, layer #endif // SLIC3R_DEBUG ); + // Insert new layer even if there is no interface generated: Likely the support angle is not steep enough to require dense interface, + // however generating a sparse support will be useful for the object stability. + // if (! new_layer->polygons.empty()) contact_out[layer_id * 2] = new_layer; if (bridging_layer != nullptr) { bridging_layer->polygons = new_layer->polygons; @@ -1944,6 +1969,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Compress contact_out, remove the nullptr items. remove_nulls(contact_out); + // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), + // the top contact layer is merged into the bottom contact layer. merge_contact_layers(m_slicing_params, m_support_params.support_layer_height_min, contact_out); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; @@ -2709,6 +2736,7 @@ void PrintObjectSupportMaterial::generate_base_layers( ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons layer_intermediate.layer_type = sltBase; + // For snug supports, expand the interfaces into the intermediate layer to make it printable. #if 0 // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); // Fillet the base polygons and trim them again with the top, interface and contact layers. @@ -2981,6 +3009,7 @@ std::pairsupport_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && // Base extruder: Either "print with active extruder" not soluble. (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); + bool snug_supports = m_object_config->support_material_style.value == smsSnug; int num_interface_layers_top = m_object_config->support_material_interface_layers; int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; if (num_interface_layers_bottom < 0) @@ -3023,7 +3052,7 @@ std::pair(0, int(intermediate_layers.size())), [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, - &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { + snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below // this intermediate layer. // Index of the first top contact layer intersecting the current intermediate layer. @@ -3055,7 +3084,10 @@ std::pair top_z) break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, + // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. + // For grid supports, merging of support regions will be performed by the projection into grid. + snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); } } if (num_interface_layers_bottom > 0) { @@ -3799,7 +3831,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density < 1.05 ? ipRectilinear : ipSupportBase); + InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density > 0.95 ? ipRectilinear : ipSupportBase); std::vector angles; angles.push_back(base_angle); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 7f24b6ede..b22448a88 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -78,6 +78,9 @@ //==================== #define ENABLE_2_4_0_ALPHA4 1 +// Enable rendering modifiers and similar objects always as transparent +#define ENABLE_MODIFIERS_ALWAYS_TRANSPARENT (1 && ENABLE_2_4_0_ALPHA4) + // Enable the fix for the detection of the out of bed state for sinking objects // and detection of out of bed using the bed perimeter #define ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS (1 && ENABLE_2_4_0_ALPHA4) diff --git a/src/miniz/CMakeLists.txt b/src/miniz/CMakeLists.txt index a664f7460..04d562b76 100644 --- a/src/miniz/CMakeLists.txt +++ b/src/miniz/CMakeLists.txt @@ -3,29 +3,15 @@ project(miniz) add_library(miniz INTERFACE) -if(NOT SLIC3R_STATIC OR CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_package(miniz 2.1 QUIET) -endif() - -if(miniz_FOUND) - - message(STATUS "Using system miniz...") - target_link_libraries(miniz INTERFACE miniz::miniz) - -else() - - add_library(miniz_static STATIC - miniz.c - miniz.h - ) - - if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU") - target_compile_definitions(miniz_static PRIVATE _GNU_SOURCE) - endif() - - target_link_libraries(miniz INTERFACE miniz_static) - target_include_directories(miniz INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) - - message(STATUS "Miniz NOT found in system, using bundled version...") +add_library(miniz_static STATIC + miniz.c + miniz.h +) +if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU") + target_compile_definitions(miniz_static PRIVATE _GNU_SOURCE) endif() + +target_link_libraries(miniz INTERFACE miniz_static) +target_include_directories(miniz INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index d6999df61..a3613edcf 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -479,9 +479,15 @@ std::array color_from_model_volume(const ModelVolume& model_volume) color[2] = 0.2f; } else if (model_volume.is_modifier()) { +#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 0.2f; +#else color[0] = 0.2f; color[1] = 1.0f; color[2] = 0.2f; +#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT } else if (model_volume.is_support_blocker()) { color[0] = 1.0f; @@ -971,7 +977,15 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_CULL_FACE)); for (GLVolumeWithIdAndZ& volume : to_render) { +#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT + if (type == ERenderType::Transparent) + volume.first->force_transparent = true; +#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT volume.first->set_render_color(); +#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT + if (type == ERenderType::Transparent) + volume.first->force_transparent = false; +#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT // render sinking contours of non-hovered volumes if (m_show_sinking_contours) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 77679a1ad..d0b29165c 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -38,6 +38,14 @@ using GUI::format_wxstr; namespace DoubleSlider { +constexpr double min_delta_area = scale_(scale_(25)); // equal to 25 mm2 +constexpr double miscalculation = scale_(scale_(1)); // equal to 1 mm2 + +bool equivalent_areas(const double& bottom_area, const double& top_area) +{ + return fabs(bottom_area - top_area) <= miscalculation; +} + wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); static std::string gcode(Type type) @@ -2034,6 +2042,32 @@ void Control::show_cog_icon_context_menu() GUI::wxGetApp().plater()->PopupMenu(&menu); } +bool check_color_change(PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, std::function break_condition) +{ + double prev_area = area(object->get_layer(frst_layer_id)->lslices); + + bool detected = false; + for (size_t i = frst_layer_id+1; i < layers_cnt; i++) { + Layer* layer = object->get_layer(i); + double cur_area = area(layer->lslices); + + // check for overhangs + if (check_overhangs && cur_area > prev_area && !equivalent_areas(prev_area, cur_area)) + break; + + // Check percent of the area decrease. + // This value have to be more than min_delta_area and more then 10% + if ((prev_area - cur_area > min_delta_area) && (cur_area / prev_area < 0.9)) { + detected = true; + if (break_condition(layer)) + break; + } + + prev_area = cur_area; + } + return detected; +} + void Control::auto_color_change() { if (!m_ticks.empty()) { @@ -2049,45 +2083,33 @@ void Control::auto_color_change() int extruder = 2; const Print& print = GUI::wxGetApp().plater()->fff_print(); - double delta_area = scale_(scale_(25)); // equal to 25 mm2 - for (auto object : print.objects()) { if (object->layer_count() == 0) continue; - double prev_area = area(object->get_layer(0)->lslices); - for (size_t i = 1; i < object->layers().size(); i++) { - Layer* layer = object->get_layer(i); - double cur_area = area(layer->lslices); - - if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) - break; - - if (prev_area - cur_area > delta_area) { - // Check percent of the area decrease. - // Ignore it, if this value is less than 10% - if (cur_area / prev_area > 0.9) - continue; - int tick = get_tick_from_value(layer->print_z); - if (tick >= 0 && !m_ticks.has_tick(tick)) { - if (m_mode == SingleExtruder) { - m_ticks.set_default_colors(true); - m_ticks.add_tick(tick, ColorChange, 1, layer->print_z); - } - else { - m_ticks.add_tick(tick, ToolChange, extruder, layer->print_z); - if (++extruder > extruders_cnt) + check_color_change(object, 1, object->layers().size(), false, [this, extruders_cnt](Layer* layer) + { + int tick = get_tick_from_value(layer->print_z); + if (tick >= 0 && !m_ticks.has_tick(tick)) { + if (m_mode == SingleExtruder) { + m_ticks.set_default_colors(true); + m_ticks.add_tick(tick, ColorChange, 1, layer->print_z); + } + else { + int extruder = 2; + if (!m_ticks.empty()) { + auto it = m_ticks.ticks.end(); + it--; + extruder = it->extruder + 1; + if (extruder > extruders_cnt) extruder = 1; } + m_ticks.add_tick(tick, ToolChange, extruder, layer->print_z); } - - // allow max 3 auto color changes - if (m_ticks.ticks.size() == 3) - break; } - - prev_area = cur_area; - } + // allow max 3 auto color changes + return m_ticks.ticks.size() > 2; + }); } if (m_ticks.empty()) diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 0f663f663..8f88de472 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -17,6 +17,8 @@ class wxMenu; namespace Slic3r { using namespace CustomGCode; +class PrintObject; +class Layer; namespace DoubleSlider { @@ -25,6 +27,15 @@ namespace DoubleSlider { */ constexpr double epsilon() { return 0.0011; } +// return true when areas are mostly equivalent +bool equivalent_areas(const double& bottom_area, const double& top_area); + +// return true if color change was detected +bool check_color_change(PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, + // what to do with detected color change + // and return true when detection have to be desturbed + std::function break_condition); + // custom message the slider sends to its parent to notify a tick-change: wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2d8c2f9bd..b0a83d93f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1294,12 +1294,14 @@ bool GLCanvas3D::is_reload_delayed() const void GLCanvas3D::enable_layers_editing(bool enable) { m_layers_editing.set_enabled(enable); +#if !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); for (unsigned int idx : idxs) { GLVolume* v = m_volumes.volumes[idx]; if (v->is_modifier) v->force_transparent = enable; } +#endif // !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT set_as_dirty(); } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e2a9df25d..a19858eea 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -865,8 +865,11 @@ bool GUI_App::on_init_inner() wxInitAllImageHandlers(); #ifdef _MSW_DARK_MODE - if (app_config->get("dark_color_mode") == "1") + if (bool dark_mode = app_config->get("dark_color_mode") == "1") { NppDarkMode::InitDarkMode(); + if (dark_mode != NppDarkMode::IsDarkMode()) + NppDarkMode::SetDarkMode(dark_mode); + } #endif SplashScreen* scrn = nullptr; if (app_config->get("show_splash_screen") == "1") { @@ -915,6 +918,23 @@ bool GUI_App::on_init_inner() } } }); + Bind(EVT_SLIC3R_ALPHA_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + app_config->save(); + if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") { + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAlphaAvailable); + } + } + }); + Bind(EVT_SLIC3R_BETA_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + app_config->save(); + if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") { + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { + this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::NewAlphaAvailable); + this->plater_->get_notification_manager()->push_notification(NotificationType::NewBetaAvailable); + } + } + }); } else { #ifdef __WXMSW__ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index e6e7336f7..83f9d1ec1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1405,9 +1405,11 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false item = m_objects_model->GetItemById(obj_idx); std::vector volumes; + // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common + /* if (type == ModelVolumeType::MODEL_PART) load_part(*(*m_objects)[obj_idx], volumes, type, from_galery); - else + else*/ load_modifier(*(*m_objects)[obj_idx], volumes, type, from_galery); if (volumes.empty()) @@ -1430,8 +1432,8 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false selection_changed(); } - -void ObjectList::load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery/* = false*/) +/* +void ObjectList::load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery/* = false* /) { if (type != ModelVolumeType::MODEL_PART) return; @@ -1489,11 +1491,12 @@ void ObjectList::load_part(ModelObject& model_object, std::vector& } } } - +*/ void ObjectList::load_modifier(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery) { - if (type == ModelVolumeType::MODEL_PART) - return; + // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common + //if (type == ModelVolumeType::MODEL_PART) + // return; wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index cc619fc45..535bfa7a7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -249,7 +249,8 @@ public: bool is_instance_or_object_selected(); void load_subobject(ModelVolumeType type, bool from_galery = false); - void load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); + // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common + //void load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); void load_modifier(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_shape_object(const std::string &type_name); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 57840dda2..8ca2a39c5 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -13,6 +13,7 @@ #include "DoubleSlider.hpp" #include "Plater.hpp" #include "MainFrame.hpp" +#include "format.hpp" #include #include @@ -687,7 +688,6 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (m_layers_slider->IsNewPrint()) { const Print& print = wxGetApp().plater()->fff_print(); - double delta_area = scale_(scale_(25)); // equal to 25 mm2 //bool is_possible_auto_color_change = false; for (auto object : print.objects()) { @@ -708,7 +708,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee int i, min_solid_height = int(0.25 * num_layers); for (i = 1; i <= min_solid_height; ++ i) { double cur_area = area(object->get_layer(i)->lslices); - if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) { + if (!DoubleSlider::equivalent_areas(bottom_area, cur_area)) { // but due to the elephant foot compensation, the first layer may be slightly smaller than the others if (i == 1 && fabs(cur_area - bottom_area) / bottom_area < 0.1) { // So, let process this case and use second layer as a bottom @@ -721,33 +721,23 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (i < min_solid_height) continue; - // bottom layer have to be a biggest, so control relation between bottom layer and object size - double prev_area = area(object->get_layer(i)->lslices); - for ( i++; i < num_layers; i++) { - double cur_area = area(object->get_layer(i)->lslices); - if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) - break; - prev_area = cur_area; - } - if (i < num_layers) - continue; - - double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); - if( bottom_area - top_area > delta_area) { - NotificationManager *notif_mngr = wxGetApp().plater()->get_notification_manager(); + if (DoubleSlider::check_color_change(object, i, num_layers, true, [this, object](Layer*) { + NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); notif_mngr->push_notification( NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel, - _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", - _u8L("Apply auto color change to print"), + _u8L("NOTE:") + "\n" + + format(_u8L("Sliced object \"%1%\" looks like a logo or a sign"), object->model_object()->name) + "\n", + _u8L("Apply automatic color change"), [this](wxEvtHandler*) { m_layers_slider->auto_color_change(); return true; }); notif_mngr->apply_in_preview(); - + return true; + }) ) + // first object with color chnages is found break; - } } } diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index 5429e2658..fef131b88 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -66,7 +66,7 @@ bool GalleryDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& f GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) : - DPIDialog(parent, wxID_ANY, _L("Shapes Gallery"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + DPIDialog(parent, wxID_ANY, _L("Shape Gallery"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { #ifndef _WIN32 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -75,7 +75,7 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Select shape from the gallery") + ":"); - m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(55 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()), + m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(50 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()), wxLC_ICON | wxSIMPLE_BORDER); m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this); @@ -152,7 +152,7 @@ void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { ID_BTN_ADD_CUSTOM_SHAPE, ID_BTN_DEL_CUSTOM_SHAPE, ID_BTN_REPLACE_CUSTOM_PNG, wxID_OK, wxID_CLOSE }); - wxSize size = wxSize(55 * em, 35 * em); + wxSize size = wxSize(50 * em, 35 * em); m_list_ctrl->SetMinSize(size); m_list_ctrl->SetSize(size); @@ -461,8 +461,11 @@ void GalleryDialog::replace_custom_png(wxEvent& event) } try { + fs::path png_path = fs::path(get_dir(false) / m_selected_items[0].name); + png_path.replace_extension("png"); + fs::path current = fs::path(into_u8(input_files.Item(0))); - fs::copy_file(current, get_dir(false) / (m_selected_items[0].name + ".png"), fs::copy_option::overwrite_if_exists); + fs::copy_file(current, png_path, fs::copy_option::overwrite_if_exists); } catch (fs::filesystem_error const& e) { std::cerr << e.what() << '\n'; @@ -535,12 +538,12 @@ bool GalleryDialog::load_files(const wxArrayString& input_files) if (!fs::exists(dest_dir / current.filename())) fs::copy_file(current, dest_dir / current.filename()); else { - std::string filename = current.filename().string(); + std::string filename = current.stem().string(); int file_idx = 0; for (auto& dir_entry : fs::directory_iterator(dest_dir)) if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) { - std::string name = dir_entry.path().filename().string(); + std::string name = dir_entry.path().stem().string(); if (filename == name) { if (file_idx == 0) file_idx++; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 66e22c694..799c4e793 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1400,7 +1400,7 @@ void MainFrame::init_menubar_as_editor() } windowMenu->AppendSeparator(); - append_menu_item(windowMenu, wxID_ANY, _L("Modify Shapes Gallery"), _L("Open the dialog to modify shapes gallery"), + append_menu_item(windowMenu, wxID_ANY, _L("Shape Gallery"), _L("Open the dialog to modify shape gallery"), [this](wxCommandEvent&) { GalleryDialog dlg(this, true); if (dlg.ShowModal() == wxID_OK) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index ed6f11ddb..b12256cf0 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -43,6 +43,10 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat }, {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, + {NotificationType::NewAlphaAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New alpha release is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { + wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, + {NotificationType::NewBetaAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New beta release is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { + wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, {NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10, _u8L("You have just added a G-code for color change, but its value is empty.\n" "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index ad2e315b7..00065f795 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -52,6 +52,9 @@ enum class NotificationType // Notification on the start of PrusaSlicer, when a new PrusaSlicer version is published. // Contains a hyperlink to open a web browser pointing to the PrusaSlicer download location. NewAppAvailable, + // Like NewAppAvailable but with text and link for alpha / bet release + NewAlphaAvailable, + NewBetaAvailable, // Notification on the start of PrusaSlicer, when updates of system profiles are detected. // Contains a hyperlink to execute installation of the new system profiles. PresetUpdateAvailable, diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index f01a1e962..9c31f679b 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -72,6 +72,11 @@ void OG_CustomCtrl::init_ctrl_lines() const std::vector& og_lines = opt_group->get_lines(); for (const Line& line : og_lines) { + if (line.is_separator()) { + ctrl_lines.emplace_back(CtrlLine(0, this, line)); + continue; + } + if (line.full_width && ( // description line line.widget != nullptr || @@ -124,6 +129,15 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) line_height = win_height; }; + auto correct_horiz_pos = [this](int& h_pos, Field* field) { + if (m_max_win_width > 0 && field->getWindow()) { + int win_width = field->getWindow()->GetSize().GetWidth(); + if (dynamic_cast(field)) + win_width *= 0.5; + h_pos += m_max_win_width - win_width; + } + }; + for (CtrlLine& ctrl_line : ctrl_lines) { if (&ctrl_line.og_line == &line) { @@ -160,6 +174,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) h_pos += 3 * blinking_button_width; Field* field = opt_group->get_field(option_set.front().opt_id); correct_line_height(ctrl_line.height, field->getWindow()); + correct_horiz_pos(h_pos, field); break; } @@ -189,8 +204,10 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) } h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width; - if (field == field_in) + if (field == field_in) { + correct_horiz_pos(h_pos, field); break; + } if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) h_pos += 2 * blinking_button_width; @@ -361,6 +378,28 @@ void OG_CustomCtrl::correct_widgets_position(wxSizer* widget, const Line& line, } }; +void OG_CustomCtrl::init_max_win_width() +{ + if (opt_group->ctrl_horiz_alignment == wxALIGN_RIGHT && m_max_win_width == 0) + for (CtrlLine& line : ctrl_lines) { + if (int max_win_width = line.get_max_win_width(); + m_max_win_width < max_win_width) + m_max_win_width = max_win_width; + } +} + +void OG_CustomCtrl::set_max_win_width(int max_win_width) +{ + if (m_max_win_width == max_win_width) + return; + m_max_win_width = max_win_width; + for (CtrlLine& line : ctrl_lines) + line.correct_items_positions(); + + GetParent()->Layout(); +} + + void OG_CustomCtrl::msw_rescale() { #ifdef __WXOSX__ @@ -374,6 +413,8 @@ void OG_CustomCtrl::msw_rescale() m_bmp_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize(); m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize(); + m_max_win_width = 0; + wxCoord v_pos = 0; for (CtrlLine& line : ctrl_lines) { line.msw_rescale(); @@ -407,6 +448,21 @@ OG_CustomCtrl::CtrlLine::CtrlLine( wxCoord height, } } +int OG_CustomCtrl::CtrlLine::get_max_win_width() +{ + int max_win_width = 0; + if (!draw_just_act_buttons) { + const std::vector