Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2021-10-14 10:48:46 +02:00
commit 7be17d89e6
39 changed files with 651 additions and 255 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

BIN
resources/shapes/cone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
resources/shapes/cone.stl Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

BIN
resources/shapes/torus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
resources/shapes/torus.stl Normal file

Binary file not shown.

View File

@ -122,6 +122,9 @@ void AppConfig::set_defaults()
if (get("auto_toolbar_size").empty()) if (get("auto_toolbar_size").empty())
set("auto_toolbar_size", "100"); set("auto_toolbar_size", "100");
if (get("notify_release").empty())
set("notify_release", "all"); // or "none" or "release"
#if ENABLE_ENVIRONMENT_MAP #if ENABLE_ENVIRONMENT_MAP
if (get("use_environment_map").empty()) if (get("use_environment_map").empty())
set("use_environment_map", "0"); set("use_environment_map", "0");

View File

@ -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()) 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]); 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 // 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) { 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(); 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 Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id];
const std::optional<Vec3f> first_vertex = m_seams_detector.get_first_vertex(); const std::optional<Vec3f> 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 // 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) { if ((new_pos - *first_vertex).squaredNorm() < 0.0625f) {
set_end_position(0.5f * (new_pos + *first_vertex)); set_end_position(0.5f * (new_pos + *first_vertex));
store_move_vertex(EMoveType::Seam); store_move_vertex(EMoveType::Seam);
@ -2697,6 +2698,10 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
m_seams_detector.activate(false); 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
store_move_vertex(type); store_move_vertex(type);

View File

@ -47,7 +47,7 @@ private:
public: public:
// Forwarded constructor // Forwarded constructor
template<class... Args> template<class... Args>
inline CachedObject(Setter fn, Args &&... args) inline CachedObject(Setter &&fn, Args &&... args)
: m_obj(std::forward<Args>(args)...), m_valid(false), m_setter(fn) : m_obj(std::forward<Args>(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 next retrieval (Setter will be called). The data that is used in
// the setter function should be guarded as well during modification so // the setter function should be guarded as well during modification so
// the modification has to take place in fn. // the modification has to take place in fn.
inline void invalidate(std::function<void()> fn) template<class Fn> void invalidate(Fn &&fn)
{ {
std::lock_guard<SpinMutex> lck(m_lck); std::lock_guard<SpinMutex> lck(m_lck);
fn(); fn();

View File

@ -190,8 +190,7 @@ static inline std::vector<ColoredLine> to_lines(const std::vector<std::vector<Co
return lines; return lines;
} }
// Double vertex equal to a coord_t point after conversion to double. static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Vec2d &ipt)
static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Point &ipt)
{ {
// Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare. // Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare.
// This should work with any settings of math compiler switches and the C++ compiler // This should work with any settings of math compiler switches and the C++ compiler
@ -199,11 +198,11 @@ static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const
using ulp_cmp_type = boost::polygon::detail::ulp_comparison<double>; using ulp_cmp_type = boost::polygon::detail::ulp_comparison<double>;
ulp_cmp_type ulp_cmp; ulp_cmp_type ulp_cmp;
static constexpr int ULPS = boost::polygon::voronoi_diagram_traits<double>::vertex_equality_predicate_type::ULPS; static constexpr int ULPS = boost::polygon::voronoi_diagram_traits<double>::vertex_equality_predicate_type::ULPS;
return ulp_cmp(vertex.x(), double(ipt.x()), ULPS) == ulp_cmp_type::EQUAL && return ulp_cmp(vertex.x(), ipt.x(), ULPS) == ulp_cmp_type::EQUAL &&
ulp_cmp(vertex.y(), double(ipt.y()), 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); 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<double>::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } static inline Point mk_point(const voronoi_diagram<double>::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<double>::vertex_type *point) { return {point->x(), point->y()}; } static inline Vec2d mk_vec2(const voronoi_diagram<double>::vertex_type *point) { return {point->x(), point->y()}; }
struct MMU_Graph struct MMU_Graph
@ -528,7 +529,7 @@ struct MMU_Graph
struct Node struct Node
{ {
Point point; Vec2d point;
std::list<size_t> arc_idxs; std::list<size_t> arc_idxs;
void remove_edge(const size_t to_idx, MMU_Graph &graph) void remove_edge(const size_t to_idx, MMU_Graph &graph)
@ -665,48 +666,67 @@ struct MMU_Graph
struct CPoint struct CPoint
{ {
CPoint() = delete; 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 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 Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {} 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; const Point m_point;
size_t m_point_idx; size_t m_point_idx;
size_t m_contour_idx; size_t m_contour_idx;
[[nodiscard]] const Vec2d &point_double() const { return m_point_double; }
[[nodiscard]] const Point &point() const { return m_point; } [[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(); }}; struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }};
typedef ClosestPointInRadiusLookup<CPoint, CPointAccessor> CPointLookupType; typedef ClosestPointInRadiusLookup<CPoint, CPointAccessor> 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)); CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON));
for (const Polygon &polygon : color_poly_tmp) for (const Polygon &polygon : color_poly_tmp)
for (const Point &pt : polygon.points) 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<double>::vertex_type &vertex : vd.vertices()) { for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
vertex.color(-1); 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 Vec2d &first_point_double = 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 &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()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->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); 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()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->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); vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
} else if (bbox.contains(vertex_point)) { } 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)) { 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)); 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)) { } 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, this->nodes_count())); closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count()));
vertex.color(this->nodes_count()); vertex.color(this->nodes_count());
this->nodes.push_back({vertex_point}); this->nodes.push_back({vertex_point_double});
} else { } 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<std::pair<const CPoint *, double>> all_closes_c_points = closest_voronoi_point.find_all(vertex_point);
int merge_to_point = -1;
for (const std::pair<const CPoint *, double> &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::vector<std::vector<Col
MMU_Graph graph; MMU_Graph graph;
graph.nodes.reserve(points.size() + vd.vertices().size()); graph.nodes.reserve(points.size() + vd.vertices().size());
for (const Point &point : points) for (const Point &point : points)
graph.nodes.push_back({point}); graph.nodes.push_back({Vec2d(double(point.x()), double(point.y()))});
graph.add_contours(color_poly); graph.add_contours(color_poly);
init_polygon_indices(graph, color_poly, lines_colored); init_polygon_indices(graph, color_poly, lines_colored);
@ -984,8 +1004,10 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
} }
} else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { } else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) {
mark_processed(edge_it); mark_processed(edge_it);
Point real_v0 = graph.nodes[edge_it->vertex0()->color()].point; Vec2d real_v0_double = graph.nodes[edge_it->vertex0()->color()].point;
Point real_v1 = graph.nodes[edge_it->vertex1()->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)) { if (is_point_closer_to_beginning_of_line(contour_line, intersection)) {
Line first_part(intersection, real_v0); Line first_part(intersection, real_v0);
@ -999,8 +1021,9 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
graph.append_edge(edge_it->vertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx); graph.append_edge(edge_it->vertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx);
} }
} else { } else {
const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx; 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 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 first_part(int_point, real_v0);
const Line second_part(int_point, real_v1); const Line second_part(int_point, real_v1);
@ -1039,12 +1062,12 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
return graph; return graph;
} }
static inline Polygon to_polygon(const Lines &lines) static inline Polygon to_polygon(const std::vector<Linef> &lines)
{ {
Polygon poly_out; Polygon poly_out;
poly_out.points.reserve(lines.size()); poly_out.points.reserve(lines.size());
for (const Line &line : lines) for (const Linef &line : lines)
poly_out.points.emplace_back(line.a); poly_out.points.emplace_back(mk_point(line.a));
return poly_out; return poly_out;
} }
@ -1056,7 +1079,7 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
{ {
std::vector<bool> used_arcs(graph.arcs.size(), false); std::vector<bool> used_arcs(graph.arcs.size(), false);
// When there is no next arc, then is returned original_arc or edge with is marked as used // 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<std::pair<const MMU_Graph::Arc *, double>> sorted_arcs; std::vector<std::pair<const MMU_Graph::Arc *, double>> sorted_arcs;
for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) { for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) {
const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; const MMU_Graph::Arc &arc = graph.arcs[arc_idx];
@ -1064,8 +1087,8 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
continue; continue;
assert(original_arc.to_idx == arc.from_idx); assert(original_arc.to_idx == arc.from_idx);
Vec2d process_line_vec_n = (process_line.a - process_line.b).cast<double>().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).cast<double>().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)); 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) if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0)
@ -1098,17 +1121,17 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
for (const size_t &arc_idx : node.arc_idxs) { for (const size_t &arc_idx : node.arc_idxs) {
const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; 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;
Linef process_line(node.point, graph.nodes[arc.to_idx].point);
Line process_line(node.point, graph.nodes[arc.to_idx].point);
used_arcs[arc_idx] = true; used_arcs[arc_idx] = true;
Lines face_lines; std::vector<Linef> face_lines;
face_lines.emplace_back(process_line); 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; const MMU_Graph::Arc *p_arc = &arc;
do { do {
const MMU_Graph::Arc &next = get_next(p_vec, *p_arc); const MMU_Graph::Arc &next = get_next(p_vec, *p_arc);
@ -1118,7 +1141,7 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
break; break;
used_arcs[next_arc_idx] = true; 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; p_arc = &next;
} while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx])); } 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; used_arcs[start_arc_idx] = true;
const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx]; const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx];
size_t idx = start_idx; size_t idx = start_idx;
double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast<double>().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) { while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) {
bool found = false; bool found = false;
for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) { 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) { 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); Linef 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 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<double>(); Vec2d first_line_vec = (first_line.a - first_line.b);
Vec2d second_line_vec = (second_line.b - second_line.a).cast<double>(); Vec2d second_line_vec = (second_line.b - second_line.a);
Vec2d first_line_vec_n = first_line_vec.normalized(); Vec2d first_line_vec_n = first_line_vec.normalized();
Vec2d second_line_vec_n = second_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)); 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; idx = arc->to_idx;
arc = &arc_n; arc = &arc_n;
line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast<double>().norm(); line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();
used_arcs[arc_idx] = true; used_arcs[arc_idx] = true;
found = true; found = true;
break; break;
@ -1185,7 +1208,7 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
for (const std::pair<size_t, size_t> &colored_segment : colored_segment_p) { for (const std::pair<size_t, size_t> &colored_segment : colored_segment_p) {
size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first); 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]); 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) { if (graph.nodes[first_idx].arc_idxs.size() >= 3) {
std::vector<std::pair<MMU_Graph::Arc *, double>> arc_to_check; std::vector<std::pair<MMU_Graph::Arc *, double>> 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 MMU_Graph::Node &node : graph.nodes)
for (const size_t &arc_idx : node.arc_idxs) { for (const size_t &arc_idx : node.arc_idxs) {
const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; 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())) 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); svg.draw(arc_line, colors[arc.color], stroke_width);
else else

View File

@ -138,14 +138,14 @@ Transform3d SLAPrint::sla_trafo(const ModelObject &model_object) const
offset(1) = 0.; offset(1) = 0.;
rotation(2) = 0.; rotation(2) = 0.;
offset(Z) *= corr(Z); offset.z() *= corr.z();
auto trafo = Transform3d::Identity(); auto trafo = Transform3d::Identity();
trafo.translate(offset); trafo.translate(offset);
trafo.scale(corr); trafo.scale(corr);
trafo.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); trafo.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()));
trafo.rotate(Eigen::AngleAxisd(rotation(1), Vec3d::UnitY())); trafo.rotate(Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()));
trafo.rotate(Eigen::AngleAxisd(rotation(0), Vec3d::UnitX())); trafo.rotate(Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX()));
trafo.scale(model_instance.get_scaling_factor()); trafo.scale(model_instance.get_scaling_factor());
trafo.scale(model_instance.get_mirror()); trafo.scale(model_instance.get_mirror());

View File

@ -411,6 +411,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts";
// Per object layer projection of the object below the layer into print bed.
std::vector<Polygons> buildplate_covered = this->buildplate_covered(object); std::vector<Polygons> buildplate_covered = this->buildplate_covered(object);
// Determine the top contact surfaces of the support, defined as: // Determine the top contact surfaces of the support, defined as:
@ -1241,7 +1242,7 @@ namespace SupportMaterialInternal {
const PrintConfig &print_config, const PrintConfig &print_config,
const Layer &lower_layer, const Layer &lower_layer,
const Polygons &lower_layer_polygons, const Polygons &lower_layer_polygons,
LayerRegion *layerm, const LayerRegion &layerm,
float fw, float fw,
Polygons &contact_polygons) 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. // 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, 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. //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); SUPPORT_SURFACES_OFFSET_PARAMETERS);
// Collect perimeters of this layer. // Collect perimeters of this layer.
//FIXME split_at_first_point() could split a bridge mid-way //FIXME split_at_first_point() could split a bridge mid-way
#if 0 #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() // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
for (Polyline &polyline : overhang_perimeters) for (Polyline &polyline : overhang_perimeters)
polyline.points[0].x += 1; polyline.points[0].x += 1;
// Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. // 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); overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices);
#else #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 #endif
// only consider straight overhangs // 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, // 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 // so we take the largest value and also apply safety offset to be ensure no gaps
// are left in between // 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())); float w = float(std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing()));
for (Polyline &polyline : overhang_perimeters) for (Polyline &polyline : overhang_perimeters)
if (polyline.is_straight()) { if (polyline.is_straight()) {
@ -1293,8 +1294,8 @@ namespace SupportMaterialInternal {
bridges = union_(bridges); bridges = union_(bridges);
} }
// remove the entire bridges and only support the unsupported edges // remove the entire bridges and only support the unsupported edges
//FIXME the brided regions are already collected as layerm->bridged. Use it? //FIXME the brided regions are already collected as layerm.bridged. Use it?
for (const Surface &surface : layerm->fill_surfaces.surfaces) for (const Surface &surface : layerm.fill_surfaces.surfaces)
if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1)
polygons_append(bridges, surface.expolygon); polygons_append(bridges, surface.expolygon);
//FIXME add the gap filled areas. Extrude the gaps with a bridge flow? //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! //FIXME add supports at regular intervals to support long bridges!
bridges = diff(bridges, bridges = diff(bridges,
// Offset unsupported edges into polygons. // 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. // Remove bridged areas from the supported areas.
contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes);
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
static int iRun = 0; static int iRun = 0;
SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), 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(contact_polygons) }, { "contact_polygons", "blue", 0.5f } },
{ { union_ex(bridges) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
@ -1325,6 +1326,7 @@ std::vector<Polygons> PrintObjectSupportMaterial::buildplate_covered(const Print
if (buildplate_only) { if (buildplate_only) {
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - start"; BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - start";
buildplate_covered.assign(object.layers().size(), Polygons()); 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) { for (size_t layer_id = 1; layer_id < object.layers().size(); ++ layer_id) {
const Layer &lower_layer = *object.layers()[layer_id-1]; const Layer &lower_layer = *object.layers()[layer_id-1];
// Merge the new slices with the preceding slices. // Merge the new slices with the preceding slices.
@ -1368,6 +1370,8 @@ struct SlicesMarginCache
Polygons all_polygons; 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<Polygons, Polygons, Polygons, float> detect_overhangs( static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
const Layer &layer, const Layer &layer,
const size_t layer_id, const size_t layer_id,
@ -1412,7 +1416,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// Expand for better stability. // Expand for better stability.
contact_polygons = offset(overhang_polygons, scaled<float>(object_config.raft_expansion.value)); contact_polygons = offset(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
} }
else else if (! layer.regions().empty())
{ {
// Generate overhang / contact_polygons for non-raft layers. // Generate overhang / contact_polygons for non-raft layers.
const Layer &lower_layer = *layer.lower_layer; const Layer &lower_layer = *layer.lower_layer;
@ -1426,6 +1430,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
slices_margin.offset = slices_margin_offset; slices_margin.offset = slices_margin_offset;
slices_margin.polygons = (slices_margin_offset == 0.f) ? slices_margin.polygons = (slices_margin_offset == 0.f) ?
lower_layer_polygons : 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); 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 (buildplate_only && !annotations.buildplate_covered[layer_id].empty()) {
if (has_enforcer) if (has_enforcer)
@ -1437,14 +1442,14 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> 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 lower_layer_offset = 0;
float no_interface_offset = 0;
for (LayerRegion *layerm : layer.regions()) { for (LayerRegion *layerm : layer.regions()) {
// Extrusion width accounts for the roundings of the extrudates. // Extrusion width accounts for the roundings of the extrudates.
// It is the maximum widh of the extrudate. // It is the maximum widh of the extrudate.
fw = float(layerm->flow(frExternalPerimeter).scaled_width()); float fw = float(layerm->flow(frExternalPerimeter).scaled_width());
no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw);
lower_layer_offset = lower_layer_offset =
(layer_id < (size_t)object_config.support_material_enforce_layers.value) ? (layer_id < (size_t)object_config.support_material_enforce_layers.value) ?
// Enforce a full possible support, ignore the overhang angle. // Enforce a full possible support, ignore the overhang angle.
@ -1465,37 +1470,40 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// This step is done before the contact surface is calculated by growing the overhang region. // 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]); diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]);
} }
} else { } else if (support_auto) {
if (support_auto) { // Get the regions needing a suport, collapse very tiny spots.
// Get the regions needing a suport, collapse very tiny spots. //FIXME cache the lower layer offset if this layer has multiple regions.
//FIXME cache the lower layer offset if this layer has multiple regions. #if 0
#if 1 //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874
//FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 diff_polygons = offset2(
diff_polygons = offset2( diff(layerm_polygons,
diff(layerm_polygons, // Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they
offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), // are not supporting this layer.
//FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to // However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports.
// no support at all for not so steep overhangs. // For example, see GH issue #3094
- 0.1f * fw, 0.1f * fw); 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 #else
diff_polygons = diff_polygons =
diff(layerm_polygons, diff(layerm_polygons,
offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
#endif #endif
if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) {
// Don't support overhangs above the top surfaces. // Don't support overhangs above the top surfaces.
// This step is done before the contact surface is calculated by growing the overhang region. // 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]); 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 (! 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()) if (diff_polygons.empty())
@ -1523,8 +1531,9 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
if (object_config.dont_support_bridges) if (object_config.dont_support_bridges)
//FIXME Expensive, potentially not precise enough. Misses gap fill extrusions, which bridge.
SupportMaterialInternal::remove_bridges_from_contacts( 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()) if (diff_polygons.empty())
continue; continue;
@ -1579,7 +1588,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]), enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]),
// Inflate just a tiny bit to avoid intersection of the overhang areas with the object. // 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 #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), 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 } }, { { layer.lslices, { "layer.lslices", "gray", 0.2f } },
@ -1596,6 +1605,8 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
return std::make_tuple(std::move(overhang_polygons), std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); 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<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupportMaterial::MyLayer*> new_contact_layer( static inline std::pair<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupportMaterial::MyLayer*> new_contact_layer(
const PrintConfig &print_config, const PrintConfig &print_config,
const PrintObjectConfig &object_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& { 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()) if (lower_layer_polygons_for_dense_interface_cache.empty())
lower_layer_polygons_for_dense_interface_cache = 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); 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; return lower_layer_polygons_for_dense_interface_cache;
}; };
@ -1721,14 +1733,8 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
)); ));
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
if (layer_id == 0 || slicing_params.soluble_interface) { bool reduce_interfaces = layer_id > 0 && ! slicing_params.soluble_interface;
// if (no_interface_offset == 0.f) { if (reduce_interfaces) {
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 {
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. // 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()); Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface());
if (! dense_interface_polygons.empty()) { 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); 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 new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
, "top_contact_polygons3", iRun, layer_id, layer.print_z , "top_contact_polygons2", iRun, layer_id, layer.print_z
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
); );
#ifdef 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<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #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) { 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. // 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);
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.
// 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<Polygons>(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true
new_layer.enforcer_polygons = std::make_unique<Polygons>(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
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
, "top_contact_polygons5", iRun, layer_id, layer.print_z , "top_contact_polygons4", iRun, layer_id, layer.print_z
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
)); ));
if (needs_union) Polygons new_polygons;
new_layer.polygons = union_(new_layer.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 #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. // Now apply the contact areas to the layer where they need to be made.
if (! contact_polygons.empty()) { 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); 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) { if (new_layer) {
// Fill the non-bridging layer with polygons.
fill_contact_layer(*new_layer, layer_id, m_slicing_params, 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_object_config, slices_margin, overhang_polygons, contact_polygons, enforcer_polygons, lower_layer_polygons,
m_support_params.support_material_flow, no_interface_offset m_support_params.support_material_flow, no_interface_offset
@ -1927,6 +1949,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
, iRun, layer , iRun, layer
#endif // SLIC3R_DEBUG #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; contact_out[layer_id * 2] = new_layer;
if (bridging_layer != nullptr) { if (bridging_layer != nullptr) {
bridging_layer->polygons = new_layer->polygons; bridging_layer->polygons = new_layer->polygons;
@ -1944,6 +1969,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Compress contact_out, remove the nullptr items. // Compress contact_out, remove the nullptr items.
remove_nulls(contact_out); 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); 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"; 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 ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons
layer_intermediate.layer_type = sltBase; layer_intermediate.layer_type = sltBase;
// For snug supports, expand the interfaces into the intermediate layer to make it printable.
#if 0 #if 0
// coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); // 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. // Fillet the base polygons and trim them again with the top, interface and contact layers.
@ -2981,6 +3009,7 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
m_object_config->support_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && m_object_config->support_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. // 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)); (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_top = m_object_config->support_material_interface_layers;
int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers;
if (num_interface_layers_bottom < 0) if (num_interface_layers_bottom < 0)
@ -3023,7 +3052,7 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())), tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
[&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, [&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, 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<int>& range) { snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range<int>& range) {
// Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
// this intermediate layer. // this intermediate layer.
// Index of the first top contact layer intersecting the current intermediate layer. // Index of the first top contact layer intersecting the current intermediate layer.
@ -3055,7 +3084,10 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
//FIXME maybe this adds one interface layer in excess? //FIXME maybe this adds one interface layer in excess?
if (top_contact_layer.bottom_z - EPSILON > top_z) if (top_contact_layer.bottom_z - EPSILON > top_z)
break; 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) { if (num_interface_layers_bottom > 0) {
@ -3799,7 +3831,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Prepare fillers. // Prepare fillers.
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
bool with_sheath = m_object_config->support_material_with_sheath; 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<float> angles; std::vector<float> angles;
angles.push_back(base_angle); angles.push_back(base_angle);

View File

@ -78,6 +78,9 @@
//==================== //====================
#define ENABLE_2_4_0_ALPHA4 1 #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 // 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 // and detection of out of bed using the bed perimeter
#define ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS (1 && ENABLE_2_4_0_ALPHA4) #define ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS (1 && ENABLE_2_4_0_ALPHA4)

View File

@ -3,29 +3,15 @@ project(miniz)
add_library(miniz INTERFACE) add_library(miniz INTERFACE)
if(NOT SLIC3R_STATIC OR CMAKE_SYSTEM_NAME STREQUAL "Linux") add_library(miniz_static STATIC
find_package(miniz 2.1 QUIET) miniz.c
miniz.h
)
if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU")
target_compile_definitions(miniz_static PRIVATE _GNU_SOURCE)
endif() endif()
if(miniz_FOUND) target_link_libraries(miniz INTERFACE miniz_static)
target_include_directories(miniz INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
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...")
endif()

View File

@ -479,9 +479,15 @@ std::array<float, 4> color_from_model_volume(const ModelVolume& model_volume)
color[2] = 0.2f; color[2] = 0.2f;
} }
else if (model_volume.is_modifier()) { 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[0] = 0.2f;
color[1] = 1.0f; color[1] = 1.0f;
color[2] = 0.2f; color[2] = 0.2f;
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
} }
else if (model_volume.is_support_blocker()) { else if (model_volume.is_support_blocker()) {
color[0] = 1.0f; color[0] = 1.0f;
@ -971,7 +977,15 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glDisable(GL_CULL_FACE));
for (GLVolumeWithIdAndZ& volume : to_render) { 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(); 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 // render sinking contours of non-hovered volumes
if (m_show_sinking_contours) if (m_show_sinking_contours)

View File

@ -38,6 +38,14 @@ using GUI::format_wxstr;
namespace DoubleSlider { 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); wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);
static std::string gcode(Type type) static std::string gcode(Type type)
@ -2034,6 +2042,32 @@ void Control::show_cog_icon_context_menu()
GUI::wxGetApp().plater()->PopupMenu(&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<bool(Layer*)> 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() void Control::auto_color_change()
{ {
if (!m_ticks.empty()) { if (!m_ticks.empty()) {
@ -2049,45 +2083,33 @@ void Control::auto_color_change()
int extruder = 2; int extruder = 2;
const Print& print = GUI::wxGetApp().plater()->fff_print(); const Print& print = GUI::wxGetApp().plater()->fff_print();
double delta_area = scale_(scale_(25)); // equal to 25 mm2
for (auto object : print.objects()) { for (auto object : print.objects()) {
if (object->layer_count() == 0) if (object->layer_count() == 0)
continue; continue;
double prev_area = area(object->get_layer(0)->lslices);
for (size_t i = 1; i < object->layers().size(); i++) { check_color_change(object, 1, object->layers().size(), false, [this, extruders_cnt](Layer* layer)
Layer* layer = object->get_layer(i); {
double cur_area = area(layer->lslices); int tick = get_tick_from_value(layer->print_z);
if (tick >= 0 && !m_ticks.has_tick(tick)) {
if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) if (m_mode == SingleExtruder) {
break; m_ticks.set_default_colors(true);
m_ticks.add_tick(tick, ColorChange, 1, layer->print_z);
if (prev_area - cur_area > delta_area) { }
// Check percent of the area decrease. else {
// Ignore it, if this value is less than 10% int extruder = 2;
if (cur_area / prev_area > 0.9) if (!m_ticks.empty()) {
continue; auto it = m_ticks.ticks.end();
int tick = get_tick_from_value(layer->print_z); it--;
if (tick >= 0 && !m_ticks.has_tick(tick)) { extruder = it->extruder + 1;
if (m_mode == SingleExtruder) { if (extruder > extruders_cnt)
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)
extruder = 1; 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;
} }
// allow max 3 auto color changes
prev_area = cur_area; return m_ticks.ticks.size() > 2;
} });
} }
if (m_ticks.empty()) if (m_ticks.empty())

View File

@ -17,6 +17,8 @@ class wxMenu;
namespace Slic3r { namespace Slic3r {
using namespace CustomGCode; using namespace CustomGCode;
class PrintObject;
class Layer;
namespace DoubleSlider { namespace DoubleSlider {
@ -25,6 +27,15 @@ namespace DoubleSlider {
*/ */
constexpr double epsilon() { return 0.0011; } 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<bool(Layer*)> break_condition);
// custom message the slider sends to its parent to notify a tick-change: // custom message the slider sends to its parent to notify a tick-change:
wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);

View File

@ -1294,12 +1294,14 @@ bool GLCanvas3D::is_reload_delayed() const
void GLCanvas3D::enable_layers_editing(bool enable) void GLCanvas3D::enable_layers_editing(bool enable)
{ {
m_layers_editing.set_enabled(enable); m_layers_editing.set_enabled(enable);
#if !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); const Selection::IndicesList& idxs = m_selection.get_volume_idxs();
for (unsigned int idx : idxs) { for (unsigned int idx : idxs) {
GLVolume* v = m_volumes.volumes[idx]; GLVolume* v = m_volumes.volumes[idx];
if (v->is_modifier) if (v->is_modifier)
v->force_transparent = enable; v->force_transparent = enable;
} }
#endif // !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
set_as_dirty(); set_as_dirty();
} }

View File

@ -865,8 +865,11 @@ bool GUI_App::on_init_inner()
wxInitAllImageHandlers(); wxInitAllImageHandlers();
#ifdef _MSW_DARK_MODE #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(); NppDarkMode::InitDarkMode();
if (dark_mode != NppDarkMode::IsDarkMode())
NppDarkMode::SetDarkMode(dark_mode);
}
#endif #endif
SplashScreen* scrn = nullptr; SplashScreen* scrn = nullptr;
if (app_config->get("show_splash_screen") == "1") { 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 { else {
#ifdef __WXMSW__ #ifdef __WXMSW__

View File

@ -1405,9 +1405,11 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false
item = m_objects_model->GetItemById(obj_idx); item = m_objects_model->GetItemById(obj_idx);
std::vector<ModelVolume*> volumes; std::vector<ModelVolume*> volumes;
// ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common
/*
if (type == ModelVolumeType::MODEL_PART) if (type == ModelVolumeType::MODEL_PART)
load_part(*(*m_objects)[obj_idx], volumes, type, from_galery); load_part(*(*m_objects)[obj_idx], volumes, type, from_galery);
else else*/
load_modifier(*(*m_objects)[obj_idx], volumes, type, from_galery); load_modifier(*(*m_objects)[obj_idx], volumes, type, from_galery);
if (volumes.empty()) if (volumes.empty())
@ -1430,8 +1432,8 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false
selection_changed(); selection_changed();
} }
/*
void ObjectList::load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery/* = false*/) void ObjectList::load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery/* = false* /)
{ {
if (type != ModelVolumeType::MODEL_PART) if (type != ModelVolumeType::MODEL_PART)
return; return;
@ -1489,11 +1491,12 @@ void ObjectList::load_part(ModelObject& model_object, std::vector<ModelVolume*>&
} }
} }
} }
*/
void ObjectList::load_modifier(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery) void ObjectList::load_modifier(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery)
{ {
if (type == ModelVolumeType::MODEL_PART) // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common
return; //if (type == ModelVolumeType::MODEL_PART)
// return;
wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); wxWindow* parent = wxGetApp().tab_panel()->GetPage(0);

View File

@ -249,7 +249,8 @@ public:
bool is_instance_or_object_selected(); bool is_instance_or_object_selected();
void load_subobject(ModelVolumeType type, bool from_galery = false); void load_subobject(ModelVolumeType type, bool from_galery = false);
void load_part(ModelObject& model_object, std::vector<ModelVolume*>& 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<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
void load_modifier(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false); void load_modifier(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
void load_shape_object(const std::string &type_name); void load_shape_object(const std::string &type_name);

View File

@ -13,6 +13,7 @@
#include "DoubleSlider.hpp" #include "DoubleSlider.hpp"
#include "Plater.hpp" #include "Plater.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "format.hpp"
#include <wx/listbook.h> #include <wx/listbook.h>
#include <wx/notebook.h> #include <wx/notebook.h>
@ -687,7 +688,6 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
if (m_layers_slider->IsNewPrint()) if (m_layers_slider->IsNewPrint())
{ {
const Print& print = wxGetApp().plater()->fff_print(); const Print& print = wxGetApp().plater()->fff_print();
double delta_area = scale_(scale_(25)); // equal to 25 mm2
//bool is_possible_auto_color_change = false; //bool is_possible_auto_color_change = false;
for (auto object : print.objects()) { for (auto object : print.objects()) {
@ -708,7 +708,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
int i, min_solid_height = int(0.25 * num_layers); int i, min_solid_height = int(0.25 * num_layers);
for (i = 1; i <= min_solid_height; ++ i) { for (i = 1; i <= min_solid_height; ++ i) {
double cur_area = area(object->get_layer(i)->lslices); 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 // 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) { if (i == 1 && fabs(cur_area - bottom_area) / bottom_area < 0.1) {
// So, let process this case and use second layer as a bottom // So, let process this case and use second layer as a bottom
@ -721,33 +721,23 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
if (i < min_solid_height) if (i < min_solid_height)
continue; continue;
// bottom layer have to be a biggest, so control relation between bottom layer and object size if (DoubleSlider::check_color_change(object, i, num_layers, true, [this, object](Layer*) {
double prev_area = area(object->get_layer(i)->lslices); NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager();
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();
notif_mngr->push_notification( notif_mngr->push_notification(
NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel, NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
_u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", _u8L("NOTE:") + "\n" +
_u8L("Apply auto color change to print"), format(_u8L("Sliced object \"%1%\" looks like a logo or a sign"), object->model_object()->name) + "\n",
_u8L("Apply automatic color change"),
[this](wxEvtHandler*) { [this](wxEvtHandler*) {
m_layers_slider->auto_color_change(); m_layers_slider->auto_color_change();
return true; return true;
}); });
notif_mngr->apply_in_preview(); notif_mngr->apply_in_preview();
return true;
}) )
// first object with color chnages is found
break; break;
}
} }
} }

View File

@ -66,7 +66,7 @@ bool GalleryDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& f
GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/) : 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 #ifndef _WIN32
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); 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") + ":"); 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); wxLC_ICON | wxSIMPLE_BORDER);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this); m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, 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 }); 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->SetMinSize(size);
m_list_ctrl->SetSize(size); m_list_ctrl->SetSize(size);
@ -461,8 +461,11 @@ void GalleryDialog::replace_custom_png(wxEvent& event)
} }
try { 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::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) { catch (fs::filesystem_error const& e) {
std::cerr << e.what() << '\n'; std::cerr << e.what() << '\n';
@ -535,12 +538,12 @@ bool GalleryDialog::load_files(const wxArrayString& input_files)
if (!fs::exists(dest_dir / current.filename())) if (!fs::exists(dest_dir / current.filename()))
fs::copy_file(current, dest_dir / current.filename()); fs::copy_file(current, dest_dir / current.filename());
else { else {
std::string filename = current.filename().string(); std::string filename = current.stem().string();
int file_idx = 0; int file_idx = 0;
for (auto& dir_entry : fs::directory_iterator(dest_dir)) for (auto& dir_entry : fs::directory_iterator(dest_dir))
if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) { 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 (filename == name) {
if (file_idx == 0) if (file_idx == 0)
file_idx++; file_idx++;

View File

@ -1400,7 +1400,7 @@ void MainFrame::init_menubar_as_editor()
} }
windowMenu->AppendSeparator(); 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&) { [this](wxCommandEvent&) {
GalleryDialog dlg(this, true); GalleryDialog dlg(this, true);
if (dlg.ShowModal() == wxID_OK) { if (dlg.ShowModal() == wxID_OK) {

View File

@ -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) { {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; }}, 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, {NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10,
_u8L("You have just added a G-code for color change, but its value is empty.\n" _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\"") }, "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },

View File

@ -52,6 +52,9 @@ enum class NotificationType
// Notification on the start of PrusaSlicer, when a new PrusaSlicer version is published. // 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. // Contains a hyperlink to open a web browser pointing to the PrusaSlicer download location.
NewAppAvailable, 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. // Notification on the start of PrusaSlicer, when updates of system profiles are detected.
// Contains a hyperlink to execute installation of the new system profiles. // Contains a hyperlink to execute installation of the new system profiles.
PresetUpdateAvailable, PresetUpdateAvailable,

View File

@ -72,6 +72,11 @@ void OG_CustomCtrl::init_ctrl_lines()
const std::vector<Line>& og_lines = opt_group->get_lines(); const std::vector<Line>& og_lines = opt_group->get_lines();
for (const Line& line : og_lines) for (const Line& line : og_lines)
{ {
if (line.is_separator()) {
ctrl_lines.emplace_back(CtrlLine(0, this, line));
continue;
}
if (line.full_width && ( if (line.full_width && (
// description line // description line
line.widget != nullptr || line.widget != nullptr ||
@ -124,6 +129,15 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
line_height = win_height; 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<CheckBox*>(field))
win_width *= 0.5;
h_pos += m_max_win_width - win_width;
}
};
for (CtrlLine& ctrl_line : ctrl_lines) { for (CtrlLine& ctrl_line : ctrl_lines) {
if (&ctrl_line.og_line == &line) 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; h_pos += 3 * blinking_button_width;
Field* field = opt_group->get_field(option_set.front().opt_id); Field* field = opt_group->get_field(option_set.front().opt_id);
correct_line_height(ctrl_line.height, field->getWindow()); correct_line_height(ctrl_line.height, field->getWindow());
correct_horiz_pos(h_pos, field);
break; 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; 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; break;
}
if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend)
h_pos += 2 * blinking_button_width; 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() void OG_CustomCtrl::msw_rescale()
{ {
#ifdef __WXOSX__ #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_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize();
m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize(); m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize();
m_max_win_width = 0;
wxCoord v_pos = 0; wxCoord v_pos = 0;
for (CtrlLine& line : ctrl_lines) { for (CtrlLine& line : ctrl_lines) {
line.msw_rescale(); 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<Option>& option_set = og_line.get_options();
for (auto opt : option_set) {
Field* field = ctrl->opt_group->get_field(opt.opt_id);
if (field && field->getWindow())
max_win_width = field->getWindow()->GetSize().GetWidth();
}
}
return max_win_width;
}
void OG_CustomCtrl::CtrlLine::correct_items_positions() void OG_CustomCtrl::CtrlLine::correct_items_positions()
{ {
if (draw_just_act_buttons || !is_visible) if (draw_just_act_buttons || !is_visible)
@ -447,6 +503,8 @@ void OG_CustomCtrl::CtrlLine::msw_rescale()
void OG_CustomCtrl::CtrlLine::update_visibility(ConfigOptionMode mode) void OG_CustomCtrl::CtrlLine::update_visibility(ConfigOptionMode mode)
{ {
if (og_line.is_separator())
return;
const std::vector<Option>& option_set = og_line.get_options(); const std::vector<Option>& option_set = og_line.get_options();
const ConfigOptionMode& line_mode = option_set.front().opt.mode; const ConfigOptionMode& line_mode = option_set.front().opt.mode;
@ -480,8 +538,25 @@ void OG_CustomCtrl::CtrlLine::update_visibility(ConfigOptionMode mode)
correct_items_positions(); correct_items_positions();
} }
void OG_CustomCtrl::CtrlLine::render_separator(wxDC& dc, wxCoord v_pos)
{
wxPoint begin(ctrl->m_h_gap, v_pos);
wxPoint end(ctrl->GetSize().GetWidth() - ctrl->m_h_gap, v_pos);
wxPen pen, old_pen = pen = dc.GetPen();
pen.SetColour(*wxLIGHT_GREY);
dc.SetPen(pen);
dc.DrawLine(begin, end);
dc.SetPen(old_pen);
}
void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos) void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
{ {
if (is_separator()) {
render_separator(dc, v_pos);
return;
}
Field* field = ctrl->opt_group->get_field(og_line.get_options().front().opt_id); Field* field = ctrl->opt_group->get_field(og_line.get_options().front().opt_id);
bool suppress_hyperlinks = get_app_config()->get("suppress_hyperlinks") == "1"; bool suppress_hyperlinks = get_app_config()->get("suppress_hyperlinks") == "1";

View File

@ -33,6 +33,8 @@ class OG_CustomCtrl :public wxPanel
wxSize m_bmp_mode_sz; wxSize m_bmp_mode_sz;
wxSize m_bmp_blinking_sz; wxSize m_bmp_blinking_sz;
int m_max_win_width{0};
struct CtrlLine { struct CtrlLine {
wxCoord height { wxDefaultCoord }; wxCoord height { wxDefaultCoord };
OG_CustomCtrl* ctrl { nullptr }; OG_CustomCtrl* ctrl { nullptr };
@ -50,16 +52,20 @@ class OG_CustomCtrl :public wxPanel
bool draw_mode_bitmap = true); bool draw_mode_bitmap = true);
~CtrlLine() { ctrl = nullptr; } ~CtrlLine() { ctrl = nullptr; }
int get_max_win_width();
void correct_items_positions(); void correct_items_positions();
void msw_rescale(); void msw_rescale();
void update_visibility(ConfigOptionMode mode); void update_visibility(ConfigOptionMode mode);
void render_separator(wxDC& dc, wxCoord v_pos);
void render(wxDC& dc, wxCoord v_pos); void render(wxDC& dc, wxCoord v_pos);
wxCoord draw_mode_bmp(wxDC& dc, wxCoord v_pos); wxCoord draw_mode_bmp(wxDC& dc, wxCoord v_pos);
wxCoord draw_text (wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url = false); wxCoord draw_text (wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url = false);
wxPoint draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking); wxPoint draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking);
wxCoord draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id = 0); wxCoord draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id = 0);
bool launch_browser() const; bool launch_browser() const;
bool is_separator() const { return og_line.is_separator(); }
std::vector<wxRect> rects_undo_icon; std::vector<wxRect> rects_undo_icon;
std::vector<wxRect> rects_undo_to_sys_icon; std::vector<wxRect> rects_undo_to_sys_icon;
@ -86,6 +92,9 @@ public:
bool update_visibility(ConfigOptionMode mode); bool update_visibility(ConfigOptionMode mode);
void correct_window_position(wxWindow* win, const Line& line, Field* field = nullptr); void correct_window_position(wxWindow* win, const Line& line, Field* field = nullptr);
void correct_widgets_position(wxSizer* widget, const Line& line, Field* field = nullptr); void correct_widgets_position(wxSizer* widget, const Line& line, Field* field = nullptr);
void init_max_win_width();
void set_max_win_width(int max_win_width);
int get_max_win_width() { return m_max_win_width; }
void msw_rescale(); void msw_rescale();
void sys_color_changed(); void sys_color_changed();

View File

@ -126,6 +126,12 @@ bool OptionsGroup::is_legend_line()
return false; return false;
} }
void OptionsGroup::set_max_win_width(int max_win_width)
{
if (custom_ctrl)
custom_ctrl->set_max_win_width(max_win_width);
}
void OptionsGroup::show_field(const t_config_option_key& opt_key, bool show/* = true*/) void OptionsGroup::show_field(const t_config_option_key& opt_key, bool show/* = true*/)
{ {
Field* field = get_field(opt_key); Field* field = get_field(opt_key);
@ -185,8 +191,16 @@ void OptionsGroup::append_line(const Line& line)
m_options_mode.push_back(option_set[0].opt.mode); m_options_mode.push_back(option_set[0].opt.mode);
} }
void OptionsGroup::append_separator()
{
m_lines.emplace_back(Line());
}
void OptionsGroup::activate_line(Line& line) void OptionsGroup::activate_line(Line& line)
{ {
if (line.is_separator())
return;
m_use_custom_ctrl_as_parent = false; m_use_custom_ctrl_as_parent = false;
if (line.full_width && ( if (line.full_width && (
@ -396,7 +410,7 @@ void OptionsGroup::activate_line(Line& line)
} }
// create all controls for the option group from the m_lines // create all controls for the option group from the m_lines
bool OptionsGroup::activate(std::function<void()> throw_if_canceled) bool OptionsGroup::activate(std::function<void()> throw_if_canceled/* = [](){}*/, int horiz_alignment/* = wxALIGN_LEFT*/)
{ {
if (sizer)//(!sizer->IsEmpty()) if (sizer)//(!sizer->IsEmpty())
return false; return false;
@ -436,6 +450,10 @@ bool OptionsGroup::activate(std::function<void()> throw_if_canceled)
throw_if_canceled(); throw_if_canceled();
activate_line(line); activate_line(line);
} }
ctrl_horiz_alignment = horiz_alignment;
if (custom_ctrl)
custom_ctrl->init_max_win_width();
} catch (UIBuildCanceled&) { } catch (UIBuildCanceled&) {
auto p = sizer; auto p = sizer;
this->clear(); this->clear();

View File

@ -49,6 +49,7 @@ using t_option = std::unique_ptr<Option>; //!
/// Represents option lines /// Represents option lines
class Line { class Line {
bool m_is_separator{ false };
public: public:
wxString label; wxString label;
wxString label_tooltip; wxString label_tooltip;
@ -71,6 +72,9 @@ public:
} }
Line(wxString label, wxString tooltip) : Line(wxString label, wxString tooltip) :
label(_(label)), label_tooltip(_(tooltip)) {} label(_(label)), label_tooltip(_(tooltip)) {}
Line() : m_is_separator(true) {}
bool is_separator() const { return m_is_separator; }
const std::vector<widget_t>& get_extra_widgets() const {return m_extra_widgets;} const std::vector<widget_t>& get_extra_widgets() const {return m_extra_widgets;}
const std::vector<Option>& get_options() const { return m_options; } const std::vector<Option>& get_options() const { return m_options; }
@ -95,6 +99,7 @@ public:
size_t label_width = 20 ;// {200}; size_t label_width = 20 ;// {200};
wxSizer* sizer {nullptr}; wxSizer* sizer {nullptr};
OG_CustomCtrl* custom_ctrl{ nullptr }; OG_CustomCtrl* custom_ctrl{ nullptr };
int ctrl_horiz_alignment{ wxALIGN_LEFT};
column_t extra_column {nullptr}; column_t extra_column {nullptr};
t_change m_on_change { nullptr }; t_change m_on_change { nullptr };
// To be called when the field loses focus, to assign a new initial value to the field. // To be called when the field loses focus, to assign a new initial value to the field.
@ -124,12 +129,13 @@ public:
void activate_line(Line& line); void activate_line(Line& line);
// create all controls for the option group from the m_lines // create all controls for the option group from the m_lines
bool activate(std::function<void()> throw_if_canceled = [](){}); bool activate(std::function<void()> throw_if_canceled = [](){}, int horiz_alignment = wxALIGN_LEFT);
// delete all controls from the option group // delete all controls from the option group
void clear(bool destroy_custom_ctrl = false); void clear(bool destroy_custom_ctrl = false);
Line create_single_option_line(const Option& option, const wxString& path = wxEmptyString) const; Line create_single_option_line(const Option& option, const wxString& path = wxEmptyString) const;
void append_single_option_line(const Option& option, const wxString& path = wxEmptyString) { append_line(create_single_option_line(option, path)); } void append_single_option_line(const Option& option, const wxString& path = wxEmptyString) { append_line(create_single_option_line(option, path)); }
void append_separator();
// return a non-owning pointer reference // return a non-owning pointer reference
inline Field* get_field(const t_config_option_key& id) const{ inline Field* get_field(const t_config_option_key& id) const{
@ -171,6 +177,9 @@ public:
wxGridSizer* get_grid_sizer() { return m_grid_sizer; } wxGridSizer* get_grid_sizer() { return m_grid_sizer; }
const std::vector<Line>& get_lines() { return m_lines; } const std::vector<Line>& get_lines() { return m_lines; }
bool is_legend_line(); bool is_legend_line();
// if we have to set the same control alignment for different option groups,
// we have to set same max contrtol width to all of them
void set_max_win_width(int max_win_width);
protected: protected:
std::map<t_config_option_key, Option> m_options; std::map<t_config_option_key, Option> m_options;

View File

@ -8,8 +8,39 @@
#include <wx/notebook.h> #include <wx/notebook.h>
#include "Notebook.hpp" #include "Notebook.hpp"
#include "ButtonsDescription.hpp" #include "ButtonsDescription.hpp"
#include "OG_CustomCtrl.hpp"
#include <initializer_list>
namespace Slic3r { namespace Slic3r {
static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values& enum_keys_map)
{
t_config_enum_names names;
int cnt = 0;
for (const auto& kvp : enum_keys_map)
cnt = std::max(cnt, kvp.second);
cnt += 1;
names.assign(cnt, "");
for (const auto& kvp : enum_keys_map)
names[kvp.second] = kvp.first;
return names;
}
#define CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NAME) \
static t_config_enum_names s_keys_names_##NAME = enum_names_from_keys_map(s_keys_map_##NAME); \
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values() { return s_keys_map_##NAME; } \
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names() { return s_keys_names_##NAME; }
static const t_config_enum_values s_keys_map_NotifyReleaseMode = {
{"all", NotifyReleaseAll},
{"release", NotifyReleaseOnly},
{"none", NotifyReleaseNone},
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NotifyReleaseMode)
namespace GUI { namespace GUI {
PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) : PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) :
@ -39,7 +70,7 @@ static std::shared_ptr<ConfigOptionsGroup>create_options_tab(const wxString& tit
static void activate_options_tab(std::shared_ptr<ConfigOptionsGroup> optgroup) static void activate_options_tab(std::shared_ptr<ConfigOptionsGroup> optgroup)
{ {
optgroup->activate(); optgroup->activate([](){}, wxALIGN_RIGHT);
optgroup->update_visibility(comSimple); optgroup->update_visibility(comSimple);
wxBoxSizer* sizer = static_cast<wxBoxSizer*>(static_cast<wxPanel*>(optgroup->parent())->GetSizer()); wxBoxSizer* sizer = static_cast<wxBoxSizer*>(static_cast<wxPanel*>(optgroup->parent())->GetSizer());
sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10);
@ -116,6 +147,8 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "version_check"); option = Option(def, "version_check");
m_optgroup_general->append_single_option_line(option); m_optgroup_general->append_single_option_line(option);
m_optgroup_general->append_separator();
// Please keep in sync with ConfigWizard // Please keep in sync with ConfigWizard
def.label = L("Export sources full pathnames to 3mf and amf"); def.label = L("Export sources full pathnames to 3mf and amf");
def.type = coBool; def.type = coBool;
@ -141,6 +174,8 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_general->append_single_option_line(option); m_optgroup_general->append_single_option_line(option);
#endif // _WIN32 #endif // _WIN32
m_optgroup_general->append_separator();
// Please keep in sync with ConfigWizard // Please keep in sync with ConfigWizard
def.label = L("Update built-in Presets automatically"); def.label = L("Update built-in Presets automatically");
def.type = coBool; def.type = coBool;
@ -165,6 +200,8 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "show_incompatible_presets"); option = Option(def, "show_incompatible_presets");
m_optgroup_general->append_single_option_line(option); m_optgroup_general->append_single_option_line(option);
m_optgroup_general->append_separator();
def.label = L("Show drop project dialog"); def.label = L("Show drop project dialog");
def.type = coBool; def.type = coBool;
def.tooltip = L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load."); def.tooltip = L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load.");
@ -172,7 +209,6 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "show_drop_project_dialog"); option = Option(def, "show_drop_project_dialog");
m_optgroup_general->append_single_option_line(option); m_optgroup_general->append_single_option_line(option);
#if __APPLE__ #if __APPLE__
def.label = L("Allow just a single PrusaSlicer instance"); def.label = L("Allow just a single PrusaSlicer instance");
def.type = coBool; def.type = coBool;
@ -186,6 +222,8 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "single_instance"); option = Option(def, "single_instance");
m_optgroup_general->append_single_option_line(option); m_optgroup_general->append_single_option_line(option);
m_optgroup_general->append_separator();
def.label = L("Ask for unsaved changes when closing application or loading new project"); def.label = L("Ask for unsaved changes when closing application or loading new project");
def.type = coBool; def.type = coBool;
def.tooltip = L("Always ask for unsaved changes, when: \n" def.tooltip = L("Always ask for unsaved changes, when: \n"
@ -230,6 +268,8 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_general->append_single_option_line(option); m_optgroup_general->append_single_option_line(option);
#endif #endif
m_optgroup_general->append_separator();
// Show/Hide splash screen // Show/Hide splash screen
def.label = L("Show splash screen"); def.label = L("Show splash screen");
def.type = coBool; def.type = coBool;
@ -291,7 +331,15 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_gui->m_on_change = [this, tabs](t_config_option_key opt_key, boost::any value) { m_optgroup_gui->m_on_change = [this, tabs](t_config_option_key opt_key, boost::any value) {
if (opt_key == "suppress_hyperlinks") if (opt_key == "suppress_hyperlinks")
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : ""; m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "";
else else if (opt_key == "notify_release") {
int val_int = boost::any_cast<int>(value);
for (const auto& item : s_keys_map_NotifyReleaseMode) {
if (item.second == val_int) {
m_values[opt_key] = item.first;
break;
}
}
} else
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0"; m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
if (opt_key == "use_custom_toolbar_size") { if (opt_key == "use_custom_toolbar_size") {
@ -300,6 +348,8 @@ void PreferencesDialog::build(size_t selected_tab)
tabs->Layout(); tabs->Layout();
this->layout(); this->layout();
} }
}; };
def.label = L("Sequential slider applied only to top layer"); def.label = L("Sequential slider applied only to top layer");
@ -362,6 +412,8 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_gui->append_single_option_line(option); m_optgroup_gui->append_single_option_line(option);
#endif #endif
m_optgroup_gui->append_separator();
def.label = L("Show \"Tip of the day\" notification after start"); def.label = L("Show \"Tip of the day\" notification after start");
def.type = coBool; def.type = coBool;
def.tooltip = L("If enabled, useful hints are displayed at startup."); def.tooltip = L("If enabled, useful hints are displayed at startup.");
@ -369,15 +421,37 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "show_hints"); option = Option(def, "show_hints");
m_optgroup_gui->append_single_option_line(option); m_optgroup_gui->append_single_option_line(option);
ConfigOptionDef def_enum;
def_enum.label = L("Notify about new releases");
def_enum.type = coEnum;
def_enum.tooltip = L("You will be notified about new release after startup acordingly: All = Regular release and alpha / beta releases. Release only = regular release.");
def_enum.enum_keys_map = &ConfigOptionEnum<NotifyReleaseMode>::get_enum_values();
def_enum.enum_values.push_back("all");
def_enum.enum_values.push_back("release");
def_enum.enum_values.push_back("none");
def_enum.enum_labels.push_back(L("All"));
def_enum.enum_labels.push_back(L("Release only"));
def_enum.enum_labels.push_back(L("None"));
def_enum.mode = comSimple;
def_enum.set_default_value(new ConfigOptionEnum<NotifyReleaseMode>(static_cast<NotifyReleaseMode>(s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")))));
option = Option(def_enum, "notify_release");
m_optgroup_gui->append_single_option_line(option);
m_optgroup_gui->append_separator();
def.label = L("Use custom size for toolbar icons"); def.label = L("Use custom size for toolbar icons");
def.type = coBool; def.type = coBool;
def.tooltip = L("If enabled, you can change size of toolbar icons manually."); def.tooltip = L("If enabled, you can change size of toolbar icons manually.");
def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" }); def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" });
option = Option(def, "use_custom_toolbar_size"); option = Option(def, "use_custom_toolbar_size");
m_optgroup_gui->append_single_option_line(option); m_optgroup_gui->append_single_option_line(option);
} }
activate_options_tab(m_optgroup_gui); activate_options_tab(m_optgroup_gui);
// set Field for notify_release to its value to activate the object
boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release"));
m_optgroup_gui->get_field("notify_release")->set_value(val, false);
if (is_editor) { if (is_editor) {
create_icon_size_slider(); create_icon_size_slider();
@ -406,6 +480,9 @@ void PreferencesDialog::build(size_t selected_tab)
} }
#endif // ENABLE_ENVIRONMENT_MAP #endif // ENABLE_ENVIRONMENT_MAP
// update alignment of the controls for all tabs
update_ctrls_alignment();
if (selected_tab < tabs->GetPageCount()) if (selected_tab < tabs->GetPageCount())
tabs->SetSelection(selected_tab); tabs->SetSelection(selected_tab);
@ -425,6 +502,20 @@ void PreferencesDialog::build(size_t selected_tab)
this->CenterOnParent(); this->CenterOnParent();
} }
void PreferencesDialog::update_ctrls_alignment()
{
int max_ctrl_width{ 0 };
std::initializer_list<ConfigOptionsGroup*> og_list = { m_optgroup_general.get(), m_optgroup_camera.get(), m_optgroup_gui.get() };
for (auto og : og_list) {
if (int max = og->custom_ctrl->get_max_win_width();
max_ctrl_width < max)
max_ctrl_width = max;
}
if (max_ctrl_width)
for (auto og : og_list)
og->custom_ctrl->set_max_win_width(max_ctrl_width);
}
void PreferencesDialog::accept(wxEvent&) void PreferencesDialog::accept(wxEvent&)
{ {
// if (m_values.find("no_defaults") != m_values.end() // if (m_values.find("no_defaults") != m_values.end()

View File

@ -10,6 +10,13 @@
class wxColourPickerCtrl; class wxColourPickerCtrl;
namespace Slic3r { namespace Slic3r {
enum NotifyReleaseMode {
NotifyReleaseAll,
NotifyReleaseOnly,
NotifyReleaseNone
};
namespace GUI { namespace GUI {
class ConfigOptionsGroup; class ConfigOptionsGroup;
@ -39,6 +46,7 @@ public:
bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; } bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; }
bool recreate_GUI() const { return m_recreate_GUI; } bool recreate_GUI() const { return m_recreate_GUI; }
void build(size_t selected_tab = 0); void build(size_t selected_tab = 0);
void update_ctrls_alignment();
void accept(wxEvent&); void accept(wxEvent&);
protected: protected:

View File

@ -447,11 +447,25 @@ void Selection::clear()
if (m_list.empty()) if (m_list.empty())
return; return;
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
// ensure that the volumes get the proper color before next call to render (expecially needed for transparent volumes)
for (unsigned int i : m_list) {
GLVolume& volume = *(*m_volumes)[i];
volume.selected = false;
bool transparent = volume.color[3] < 1.0f;
if (transparent)
volume.force_transparent = true;
volume.set_render_color();
if (transparent)
volume.force_transparent = false;
}
#else
for (unsigned int i : m_list) { for (unsigned int i : m_list) {
(*m_volumes)[i]->selected = false; (*m_volumes)[i]->selected = false;
// ensure the volume gets the proper color before next call to render (expecially needed for transparent volumes) // ensure the volume gets the proper color before next call to render (expecially needed for transparent volumes)
(*m_volumes)[i]->set_render_color(); (*m_volumes)[i]->set_render_color();
} }
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
m_list.clear(); m_list.clear();

View File

@ -141,7 +141,8 @@ struct Updates
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent); wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_ALPHA_VERSION_ONLINE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_BETA_VERSION_ONLINE, wxCommandEvent);
struct PresetUpdater::priv struct PresetUpdater::priv
{ {
@ -167,6 +168,7 @@ struct PresetUpdater::priv
bool get_file(const std::string &url, const fs::path &target_path) const; bool get_file(const std::string &url, const fs::path &target_path) const;
void prune_tmps() const; void prune_tmps() const;
void sync_version() const; void sync_version() const;
void parse_version_string(const std::string& body) const;
void sync_config(const VendorMap vendors); void sync_config(const VendorMap vendors);
void check_install_indices() const; void check_install_indices() const;
@ -262,25 +264,70 @@ void PresetUpdater::priv::sync_version() const
}) })
.on_complete([&](std::string body, unsigned /* http_status */) { .on_complete([&](std::string body, unsigned /* http_status */) {
boost::trim(body); boost::trim(body);
const auto nl_pos = body.find_first_of("\n\r"); parse_version_string(body);
if (nl_pos != std::string::npos) {
body.resize(nl_pos);
}
if (! Semver::parse(body)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, body);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, body);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString(GUI::from_u8(body));
GUI::wxGetApp().QueueEvent(evt);
}) })
.perform_sync(); .perform_sync();
} }
// Parses version string obtained in sync_version() and sends events to UI thread.
// Version string must contain release version on first line. Follows non-mandatory alpha / beta releases on following lines (alpha=2.0.0-alpha1).
void PresetUpdater::priv::parse_version_string(const std::string& body) const
{
// release version
std::string version;
const auto first_nl_pos = body.find_first_of("\n\r");
if (first_nl_pos != std::string::npos)
version = body.substr(0, first_nl_pos);
else
version = body;
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
// alpha / beta version
size_t nexn_nl_pos = first_nl_pos;
while (nexn_nl_pos != std::string::npos && body.size() > nexn_nl_pos + 1) {
const auto last_nl_pos = nexn_nl_pos;
nexn_nl_pos = body.find_first_of("\n\r", last_nl_pos + 1);
std::string line;
if (nexn_nl_pos == std::string::npos)
line = body.substr(last_nl_pos + 1);
else
line = body.substr(last_nl_pos + 1, nexn_nl_pos - last_nl_pos - 1);
// alpha
if (line.substr(0, 6) == "alpha=") {
version = line.substr(6);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version of alpha release: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_ALPHA_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
// beta
}
else if (line.substr(0, 5) == "beta=") {
version = line.substr(5);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version of beta release: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_BETA_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
}
}
}
// Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Download vendor indices. Also download new bundles if an index indicates there's a new one available.
// Both are saved in cache. // Both are saved in cache.
void PresetUpdater::priv::sync_config(const VendorMap vendors) void PresetUpdater::priv::sync_config(const VendorMap vendors)

View File

@ -61,7 +61,7 @@ private:
}; };
wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent); wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_ALPHA_VERSION_ONLINE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_BETA_VERSION_ONLINE, wxCommandEvent);
} }
#endif #endif