diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 83ae32d39..3fa8d355f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -190,6 +190,8 @@ add_library(libslic3r STATIC Time.cpp Time.hpp MTUtils.hpp + VoronoiOffset.cpp + VoronoiOffset.hpp Zipper.hpp Zipper.cpp MinAreaBoundingBox.hpp diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 9fda79a13..b43f1466c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -48,15 +48,13 @@ void GCodeProcessor::apply_config(const PrintConfig& config) size_t extruders_count = config.nozzle_diameter.values.size(); m_extruder_offsets.resize(extruders_count); - for (size_t id = 0; id < extruders_count; ++id) - { + for (size_t id = 0; id < extruders_count; ++id) { Vec2f offset = config.extruder_offset.get_at(id).cast(); m_extruder_offsets[id] = Vec3f(offset(0), offset(1), 0.0f); } m_extruders_color.resize(extruders_count); - for (size_t id = 0; id < extruders_count; ++id) - { + for (size_t id = 0; id < extruders_count; ++id) { m_extruders_color[id] = static_cast(id); } } @@ -104,8 +102,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) m_start_position = m_end_position; std::string cmd = line.cmd(); - if (cmd.length() > 1) - { + if (cmd.length() > 1) { // process command lines switch (::toupper(cmd[0])) { @@ -163,8 +160,7 @@ void GCodeProcessor::process_tags(const std::string& comment) { // extrusion role tag size_t pos = comment.find(Extrusion_Role_Tag); - if (pos != comment.npos) - { + if (pos != comment.npos) { try { int role = std::stoi(comment.substr(pos + Extrusion_Role_Tag.length())); @@ -185,8 +181,7 @@ void GCodeProcessor::process_tags(const std::string& comment) // width tag pos = comment.find(Width_Tag); - if (pos != comment.npos) - { + if (pos != comment.npos) { try { m_width = std::stof(comment.substr(pos + Width_Tag.length())); @@ -200,8 +195,7 @@ void GCodeProcessor::process_tags(const std::string& comment) // height tag pos = comment.find(Height_Tag); - if (pos != comment.npos) - { + if (pos != comment.npos) { try { m_height = std::stof(comment.substr(pos + Height_Tag.length())); @@ -215,8 +209,7 @@ void GCodeProcessor::process_tags(const std::string& comment) // mm3 per mm tag pos = comment.find(Mm3_Per_Mm_Tag); - if (pos != comment.npos) - { + if (pos != comment.npos) { try { m_mm3_per_mm = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length())); @@ -230,8 +223,7 @@ void GCodeProcessor::process_tags(const std::string& comment) // color change tag pos = comment.find(Color_Change_Tag); - if (pos != comment.npos) - { + if (pos != comment.npos) { pos = comment.find_last_of(",T"); try { @@ -258,16 +250,14 @@ void GCodeProcessor::process_tags(const std::string& comment) // pause print tag pos = comment.find(Pause_Print_Tag); - if (pos != comment.npos) - { + if (pos != comment.npos) { store_move_vertex(EMoveType::Pause_Print); return; } // custom code tag pos = comment.find(Custom_Code_Tag); - if (pos != comment.npos) - { + if (pos != comment.npos) { store_move_vertex(EMoveType::Custom_GCode); return; } @@ -281,8 +271,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (axis == E) is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); - if (lineG1.has(Slic3r::Axis(axis))) - { + if (lineG1.has(Slic3r::Axis(axis))) { float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; @@ -294,32 +283,43 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) auto move_type = [this](const AxisCoords& delta_pos) { EMoveType type = EMoveType::Noop; - if (delta_pos[E] < 0.0f) - { + if (delta_pos[E] < 0.0f) { if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) type = EMoveType::Travel; else type = EMoveType::Retract; - } - else if (delta_pos[E] > 0.0f) - { + } else if (delta_pos[E] > 0.0f) { if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f) type = EMoveType::Unretract; else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f)) type = EMoveType::Extrude; - } - else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) + } else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) type = EMoveType::Travel; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) + { + if (m_extrusion_role != erCustom) + { + m_width = 0.5f; + m_height = 0.5f; + } + type = EMoveType::Travel; + } +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f || !is_valid_extrusion_role(m_extrusion_role))) type = EMoveType::Travel; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return type; }; // updates axes positions from line - for (unsigned char a = X; a <= E; ++a) - { + for (unsigned char a = X; a <= E; ++a) { m_end_position[a] = absolute_position((Axis)a, line); } @@ -330,8 +330,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // calculates movement deltas float max_abs_delta = 0.0f; AxisCoords delta_pos; - for (unsigned char a = X; a <= E; ++a) - { + for (unsigned char a = X; a <= E; ++a) { delta_pos[a] = m_end_position[a] - m_start_position[a]; max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a])); } @@ -383,38 +382,32 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; bool anyFound = false; - if (line.has_x()) - { + if (line.has_x()) { m_origin[X] = m_end_position[X] - line.x() * lengthsScaleFactor; anyFound = true; } - if (line.has_y()) - { + if (line.has_y()) { m_origin[Y] = m_end_position[Y] - line.y() * lengthsScaleFactor; anyFound = true; } - if (line.has_z()) - { + if (line.has_z()) { m_origin[Z] = m_end_position[Z] - line.z() * lengthsScaleFactor; anyFound = true; } - if (line.has_e()) - { + if (line.has_e()) { // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments, // we set the value taken from the G92 line as the new current position for it m_end_position[E] = line.e() * lengthsScaleFactor; anyFound = true; } - if (!anyFound && !line.has_unknown_axis()) - { + if (!anyFound && !line.has_unknown_axis()) { // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510, // where G92 A0 B0 is called although the extruder axis is till E. - for (unsigned char a = X; a <= E; ++a) - { + for (unsigned char a = X; a <= E; ++a) { m_origin[a] = m_end_position[a]; } } @@ -432,8 +425,7 @@ void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line) { - if (!line.has('P')) - { + if (!line.has('P')) { // The absence of P means the print cooling fan, so ignore anything else. float new_fan_speed; if (line.has_value('S', new_fan_speed)) @@ -502,8 +494,7 @@ void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line) if (m_flavor != gcfRepetier) return; - for (unsigned char a = 0; a <= 3; ++a) - { + for (unsigned char a = 0; a <= 3; ++a) { m_cached_position.position[a] = m_start_position[a]; } m_cached_position.feedrate = m_feedrate; @@ -521,10 +512,8 @@ void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) bool has_xyz = !(line.has_x() || line.has_y() || line.has_z()); float p = FLT_MAX; - for (unsigned char a = X; a <= Z; ++a) - { - if (has_xyz || line.has(a)) - { + for (unsigned char a = X; a <= Z; ++a) { + if (has_xyz || line.has(a)) { p = m_cached_position.position[a]; if (p != FLT_MAX) m_start_position[a] = p; @@ -550,18 +539,15 @@ void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_T(const std::string& command) { - if (command.length() > 1) - { + if (command.length() > 1) { try { unsigned char id = static_cast(std::stoi(command.substr(1))); - if (m_extruder_id != id) - { + if (m_extruder_id != id) { unsigned char extruders_count = static_cast(m_extruder_offsets.size()); if (id >= extruders_count) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; - else - { + else { m_extruder_id = id; m_cp_color.current = m_extruders_color[id]; } diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 40c622f04..87fb0c9c7 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -252,8 +252,16 @@ bool arrange( // output Pointfs &positions); +class VoronoiDiagram : public boost::polygon::voronoi_diagram { +public: + typedef double coord_type; + typedef boost::polygon::point_data point_type; + typedef boost::polygon::segment_data segment_type; + typedef boost::polygon::rectangle_data rect_type; +}; + class MedialAxis { - public: +public: Lines lines; const ExPolygon* expolygon; double max_width; @@ -263,14 +271,8 @@ class MedialAxis { void build(ThickPolylines* polylines); void build(Polylines* polylines); - private: - class VD : public boost::polygon::voronoi_diagram { - public: - typedef double coord_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; - }; +private: + using VD = VoronoiDiagram; VD vd; std::set edges, valid_edges; std::map > thickness; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index f5ce257cd..6b60517f6 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -48,10 +48,16 @@ // Enable smoothing of objects normals #define ENABLE_SMOOTH_NORMALS (0 && ENABLE_2_3_0_ALPHA1) +// Enable error logging for OpenGL calls when SLIC3R_LOGLEVEL >= 5 +#define ENABLE_OPENGL_ERROR_LOGGING (1 && ENABLE_2_3_0_ALPHA1) + // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (1 && ENABLE_GCODE_VIEWER) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#define ENABLE_GCODE_VIEWER_AS_STATE (1 && ENABLE_GCODE_VIEWER) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/VoronoiOffset.cpp b/src/libslic3r/VoronoiOffset.cpp new file mode 100644 index 000000000..cd96e3cdc --- /dev/null +++ b/src/libslic3r/VoronoiOffset.cpp @@ -0,0 +1,393 @@ +// Polygon offsetting code inspired by OpenVoronoi by Anders Wallin +// https://github.com/aewallin/openvoronoi +// This offsetter uses results of boost::polygon Voronoi. + +#include "VoronoiOffset.hpp" + +#include + +namespace Slic3r { + +using VD = Geometry::VoronoiDiagram; + +namespace detail { + // Intersect a circle with a ray, return the two parameters + double first_circle_segment_intersection_parameter( + const Vec2d ¢er, const double r, const Vec2d &pt, const Vec2d &v) + { + const Vec2d d = pt - center; +#ifndef NDEBUG + double d0 = (pt - center).norm(); + double d1 = (pt + v - center).norm(); + assert(r < std::max(d0, d1) + EPSILON); +#endif /* NDEBUG */ + const double a = v.squaredNorm(); + const double b = 2. * d.dot(v); + const double c = d.squaredNorm() - r * r; + std::pair> out; + double u = b * b - 4. * a * c; + assert(u > - EPSILON); + double t; + if (u <= 0) { + // Degenerate to a single closest point. + t = - b / (2. * a); + assert(t >= - EPSILON && t <= 1. + EPSILON); + return Slic3r::clamp(0., 1., t); + } else { + u = sqrt(u); + out.first = 2; + double t0 = (- b - u) / (2. * a); + double t1 = (- b + u) / (2. * a); + // One of the intersections shall be found inside the segment. + assert((t0 >= - EPSILON && t0 <= 1. + EPSILON) || (t1 >= - EPSILON && t1 <= 1. + EPSILON)); + if (t1 < 0.) + return 0.; + if (t0 > 1.) + return 1.; + return (t0 > 0.) ? t0 : t1; + } + } + + Vec2d voronoi_edge_offset_point( + const VD &vd, + const Lines &lines, + // Distance of a VD vertex to the closest site (input polygon edge or vertex). + const std::vector &vertex_dist, + // Minium distance of a VD edge to the closest site (input polygon edge or vertex). + // For a parabolic segment the distance may be smaller than the distance of the two end points. + const std::vector &edge_dist, + // Edge for which to calculate the offset point. If the distance towards the input polygon + // is not monotonical, pick the offset point closer to edge.vertex0(). + const VD::edge_type &edge, + // Distance from the input polygon along the edge. + const double offset_distance) + { + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + if (v0 == nullptr || v1 == nullptr) { + assert(edge.is_infinite()); + assert(v0 != nullptr || v1 != nullptr); + // Offsetting on an unconstrained edge. + assert(offset_distance > vertex_dist[(v0 ? v0 : v1) - &vd.vertices().front()] - EPSILON); + Vec2d pt, dir; + double t; + if (cell->contains_point() && cell2->contains_point()) { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + // Direction vector of this unconstrained Voronoi edge. + dir = Vec2d(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + if (v0 == nullptr) { + v0 = v1; + dir = - dir; + } + pt = Vec2d(v0->x(), v0->y()); + t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_linear()); + assert(edge.is_secondary()); + const Line &line = cell->contains_segment() ? line0 : line1; + const Point &ipt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); + assert(line.a == ipt || line.b == ipt); + pt = Vec2d(ipt.x(), ipt.y()); + dir = Vec2d(line.a.y() - line.b.y(), line.b.x() - line.a.x()); + assert(dir.norm() > 0.); + t = offset_distance / dir.norm(); + if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) + t = - t; + } + return pt + t * dir; + } else { + // Constrained edge. + Vec2d p0(v0->x(), v0->y()); + Vec2d p1(v1->x(), v1->y()); + double d0 = vertex_dist[v0 - &vd.vertices().front()]; + double d1 = vertex_dist[v1 - &vd.vertices().front()]; + if (cell->contains_segment() && cell2->contains_segment()) { + // This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically. + double ddif = d1 - d0; + assert(offset_distance > std::min(d0, d1) - EPSILON && offset_distance < std::max(d0, d1) + EPSILON); + double t = (ddif == 0) ? 0. : clamp(0., 1., (offset_distance - d0) / ddif); + return Slic3r::lerp(p0, p1, t); + } else { + // One cell contains a point, the other contains an edge or a point. + assert(cell->contains_point() || cell2->contains_point()); + const Point &ipt = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + double t = detail::first_circle_segment_intersection_parameter( + Vec2d(ipt.x(), ipt.y()), offset_distance, p0, p1 - p0); + return Slic3r::lerp(p0, p1, t); + } + } + } +}; + +Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance, double discretization_error) +{ + // Distance of a VD vertex to the closest site (input polygon edge or vertex). + std::vector vertex_dist(vd.num_vertices(), std::numeric_limits::max()); + + // Minium distance of a VD edge to the closest site (input polygon edge or vertex). + // For a parabolic segment the distance may be smaller than the distance of the two end points. + std::vector edge_dist(vd.num_edges(), std::numeric_limits::max()); + + // Calculate minimum distance of input polygons to voronoi vertices and voronoi edges. + for (const VD::edge_type &edge : vd.edges()) { + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + double d0, d1, dmin; + if (v0 == nullptr || v1 == nullptr) { + assert(edge.is_infinite()); + if (cell->contains_point() && cell2->contains_point()) { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + d0 = d1 = std::numeric_limits::max(); + if (v0 == nullptr && v1 == nullptr) { + dmin = (pt1.cast() - pt0.cast()).norm(); + } else { + Vec2d pt((pt0 + pt1).cast() * 0.5); + Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + Vec2d pt0d(pt0.x(), pt0.y()); + if (v0) { + Vec2d a(v0->x(), v0->y()); + d0 = (a - pt0d).norm(); + dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d0; + vertex_dist[v0 - &vd.vertices().front()] = d0; + } else { + Vec2d a(v1->x(), v1->y()); + d1 = (a - pt0d).norm(); + dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d1; + vertex_dist[v1 - &vd.vertices().front()] = d1; + } + } + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_linear()); + assert(edge.is_secondary()); +#ifndef NDEBUG + if (cell->contains_segment()) { + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || + (pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); + } else { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || + (pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); + } + const Point &pt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); +#endif /* NDEBUG */ + if (v0) { + assert((Point(v0->x(), v0->y()) - pt).cast().norm() < SCALED_EPSILON); + d0 = dmin = 0.; + vertex_dist[v0 - &vd.vertices().front()] = d0; + } else { + assert((Point(v1->x(), v1->y()) - pt).cast().norm() < SCALED_EPSILON); + d1 = dmin = 0.; + vertex_dist[v1 - &vd.vertices().front()] = d1; + } + } + } else { + // Finite edge has valid points at both sides. + if (cell->contains_segment() && cell2->contains_segment()) { + // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments. + Vec2d pt(line0.a.cast()); + Vec2d dir(line0.b.cast() - pt); + Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt; + Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt; + double l2 = dir.squaredNorm(); + assert(l2 > 0.); + d0 = (dir * (vec0.dot(dir) / l2) - vec0).norm(); + d1 = (dir * (vec1.dot(dir) / l2) - vec1).norm(); + dmin = std::min(d0, d1); + } else { + assert(cell->contains_point() || cell2->contains_point()); + const Point &pt0 = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + // Project p0 to line segment . + Vec2d p0(v0->x(), v0->y()); + Vec2d p1(v1->x(), v1->y()); + Vec2d px(pt0.x(), pt0.y()); + Vec2d v = p1 - p0; + d0 = (p0 - px).norm(); + d1 = (p1 - px).norm(); + double t = v.dot(px - p0); + double l2 = v.squaredNorm(); + if (t > 0. && t < l2) { + // Foot point on the line segment. + Vec2d foot = p0 + (t / l2) * v; + dmin = (foot - px).norm(); + } else + dmin = std::min(d0, d1); + } + vertex_dist[v0 - &vd.vertices().front()] = d0; + vertex_dist[v1 - &vd.vertices().front()] = d1; + } + edge_dist[&edge - &vd.edges().front()] = dmin; + } + + // Mark cells intersected by the offset curve. + std::vector seed_cells(vd.num_cells(), false); + for (const VD::cell_type &cell : vd.cells()) { + const VD::edge_type *first_edge = cell.incident_edge(); + const VD::edge_type *edge = first_edge; + do { + double dmin = edge_dist[edge - &vd.edges().front()]; + double dmax = std::numeric_limits::max(); + const VD::vertex_type *v0 = edge->vertex0(); + const VD::vertex_type *v1 = edge->vertex1(); + if (v0 != nullptr) + dmax = vertex_dist[v0 - &vd.vertices().front()]; + if (v1 != nullptr) + dmax = std::max(dmax, vertex_dist[v1 - &vd.vertices().front()]); + if (offset_distance >= dmin && offset_distance <= dmax) { + // This cell is being intersected by the offset curve. + seed_cells[&cell - &vd.cells().front()] = true; + break; + } + edge = edge->next(); + } while (edge != first_edge); + } + + auto edge_dir = [&vd, &vertex_dist, &edge_dist, offset_distance](const VD::edge_type *edge) { + const VD::vertex_type *v0 = edge->vertex0(); + const VD::vertex_type *v1 = edge->vertex1(); + double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits::max(); + double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits::max(); + if (d0 < offset_distance && offset_distance < d1) + return true; + else if (d1 < offset_distance && offset_distance < d0) + return false; + else { + assert(false); + return false; + } + }; + + /// \brief starting at e, find the next edge on the face that brackets t + /// + /// we can be in one of two modes. + /// if direction==false then we are looking for an edge where src_t < t < trg_t + /// if direction==true we are looning for an edge where trg_t < t < src_t + auto next_offset_edge = + [&vd, &vertex_dist, &edge_dist, offset_distance] + (const VD::edge_type *start_edge, bool direction) -> const VD::edge_type* { + const VD::edge_type *edge = start_edge; + do { + const VD::vertex_type *v0 = edge->vertex0(); + const VD::vertex_type *v1 = edge->vertex1(); + double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits::max(); + double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits::max(); + if (direction ? (d1 < offset_distance && offset_distance < d0) : (d0 < offset_distance && offset_distance < d1)) + return edge; + edge = edge->next(); + } while (edge != start_edge); + assert(false); + return nullptr; + }; + +#ifndef NDEBUG + auto dist_to_site = [&lines](const VD::cell_type &cell, const Vec2d &point) { + const Line &line = lines[cell.source_index()]; + return cell.contains_point() ? + (((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b).cast() - point).norm() : + line.distance_to(point.cast()); + }; +#endif /* NDEBUG */ + + // Track the offset curves. + Polygons out; + double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance); + double sin_threshold = sin(angle_step) + EPSILON; + for (size_t seed_cell_idx = 0; seed_cell_idx < vd.num_cells(); ++ seed_cell_idx) + if (seed_cells[seed_cell_idx]) { + seed_cells[seed_cell_idx] = false; + // Initial direction should not matter, an offset curve shall intersect a cell at least at two points + // (if it is not just touching the cell at a single vertex), and such two intersection points shall have + // opposite direction. + bool direction = false; + // the first edge on the start-face + const VD::cell_type &cell = vd.cells()[seed_cell_idx]; + const VD::edge_type *start_edge = next_offset_edge(cell.incident_edge(), direction); + assert(start_edge->cell() == &cell); + const VD::edge_type *edge = start_edge; + Polygon poly; + do { + direction = edge_dir(edge); + // find the next edge + const VD::edge_type *next_edge = next_offset_edge(edge->next(), direction); + //std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n"; + // Interpolate a circular segment or insert a linear segment between edge and next_edge. + const VD::cell_type *cell = edge->cell(); + Vec2d p1 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *edge, offset_distance); + Vec2d p2 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *next_edge, offset_distance); +#ifndef NDEBUG + { + double err = dist_to_site(*cell, p1) - offset_distance; + assert(std::abs(err) < SCALED_EPSILON); + err = dist_to_site(*cell, p2) - offset_distance; + assert(std::abs(err) < SCALED_EPSILON); + } +#endif /* NDEBUG */ + if (cell->contains_point()) { + // Discretize an arc from p1 to p2 with radius = offset_distance and discretization_error. + // The arc should cover angle < PI. + //FIXME we should be able to produce correctly oriented output curves based on the first edge taken! + const Line &line0 = lines[cell->source_index()]; + const Vec2d ¢er = ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b).cast(); + const Vec2d v1 = p1 - center; + const Vec2d v2 = p2 - center; + double orient = cross2(v1, v2); + double orient_norm = v1.norm() * v2.norm(); + bool ccw = orient > 0; + bool obtuse = v1.dot(v2) < 0.; + if (! ccw) + orient = - orient; + assert(orient != 0.); + if (obtuse || orient > orient_norm * sin_threshold) { + // Angle is bigger than the threshold, therefore the arc will be discretized. + double angle = asin(orient / orient_norm); + if (obtuse) + angle = M_PI - angle; + size_t n_steps = size_t(ceil(angle / angle_step)); + double astep = angle / n_steps; + if (! ccw) + astep *= -1.; + double a = astep; + for (size_t i = 1; i < n_steps; ++ i, a += astep) { + double c = cos(a); + double s = sin(a); + Vec2d p = center + Vec2d(c * v1.x() - s * v1.y(), s * v1.x() + c * v1.y()); + poly.points.emplace_back(Point(coord_t(p.x()), coord_t(p.y()))); + } + } + } + poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y()))); + // although we may revisit current_face (if it is non-convex), it seems safe to mark it "done" here. + seed_cells[cell - &vd.cells().front()] = false; + edge = next_edge->twin(); + } while (edge != start_edge); + out.emplace_back(std::move(poly)); + } + + return out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/VoronoiOffset.hpp b/src/libslic3r/VoronoiOffset.hpp new file mode 100644 index 000000000..9f5485c00 --- /dev/null +++ b/src/libslic3r/VoronoiOffset.hpp @@ -0,0 +1,14 @@ +#ifndef slic3r_VoronoiOffset_hpp_ +#define slic3r_VoronoiOffset_hpp_ + +#include "libslic3r.h" + +#include "Geometry.hpp" + +namespace Slic3r { + +Polygons voronoi_offset(const Geometry::VoronoiDiagram &vd, const Lines &lines, double offset_distance, double discretization_error); + +} // namespace Slic3r + +#endif // slic3r_VoronoiOffset_hpp_ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 40bb7a2cf..f53e4a55c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -45,12 +45,19 @@ #include #ifdef HAS_GLSAFE -void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name) +void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name) { +#if defined(NDEBUG) && ENABLE_OPENGL_ERROR_LOGGING + // In release mode, if OpenGL debugging was forced by ENABLE_OPENGL_ERROR_LOGGING, only show + // OpenGL errors if sufficiently high loglevel. + if (Slic3r::get_logging_level() < 5) + return; +#endif // ENABLE_OPENGL_ERROR_LOGGING + GLenum err = glGetError(); if (err == GL_NO_ERROR) return; - const char *sErr = 0; + const char* sErr = 0; switch (err) { case GL_INVALID_ENUM: sErr = "Invalid Enum"; break; case GL_INVALID_VALUE: sErr = "Invalid Value"; break; @@ -61,10 +68,10 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break; default: sErr = "Unknown"; break; } - BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr; + BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr; assert(false); } -#endif +#endif // HAS_GLSAFE namespace Slic3r { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 9555cd969..1b4d0fc4f 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -10,20 +10,20 @@ #include -#ifndef NDEBUG -#define HAS_GLSAFE +#if ENABLE_OPENGL_ERROR_LOGGING || ! defined(NDEBUG) + #define HAS_GLSAFE #endif #ifdef HAS_GLSAFE -extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); -inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } -#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) -#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) -#else -inline void glAssertRecentCall() { } -#define glsafe(cmd) cmd -#define glcheck() -#endif + extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); + inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } + #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) + #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) +#else // HAS_GLSAFE + inline void glAssertRecentCall() { } + #define glsafe(cmd) cmd + #define glcheck() +#endif // HAS_GLSAFE namespace Slic3r { class SLAPrintObject; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d4daf73a6..304bc9fe7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1518,9 +1518,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_retina_helper(nullptr) #endif , m_in_render(false) - , m_main_toolbar(GLToolbar::Normal, "Top") - , m_undoredo_toolbar(GLToolbar::Normal, "Top") - , m_collapse_toolbar(GLToolbar::Normal, "Top") + , m_main_toolbar(GLToolbar::Normal, "Main") + , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") , m_gizmos(*this) , m_use_clipping_planes(false) , m_sidebar_field("") @@ -1903,11 +1902,6 @@ void GLCanvas3D::enable_undoredo_toolbar(bool enable) m_undoredo_toolbar.set_enabled(enable); } -void GLCanvas3D::enable_collapse_toolbar(bool enable) -{ - m_collapse_toolbar.set_enabled(enable); -} - void GLCanvas3D::enable_dynamic_background(bool enable) { m_dynamic_background_enabled = enable; @@ -2108,7 +2102,7 @@ void GLCanvas3D::render() tooltip = m_undoredo_toolbar.get_tooltip(); if (tooltip.empty()) - tooltip = m_collapse_toolbar.get_tooltip(); + tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip(); if (tooltip.empty()) tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip(); @@ -2904,8 +2898,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) m_dirty |= m_main_toolbar.update_items_state(); m_dirty |= m_undoredo_toolbar.update_items_state(); - m_dirty |= m_collapse_toolbar.update_items_state(); m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); + m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); m_dirty |= mouse3d_controller_applied; @@ -3559,7 +3553,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) return; } - if (m_collapse_toolbar.on_mouse(evt, *this)) + if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) { if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); @@ -4279,7 +4273,7 @@ void GLCanvas3D::update_ui_from_settings() #endif // ENABLE_RETINA_GL bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; - enable_collapse_toolbar(enable_collapse); + wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse); } @@ -5141,51 +5135,7 @@ bool GLCanvas3D::_init_view_toolbar() bool GLCanvas3D::_init_collapse_toolbar() { - if (!m_collapse_toolbar.is_enabled() && m_collapse_toolbar.get_items_count() > 0) - return true; - - BackgroundTexture::Metadata background_data; - background_data.filename = "toolbar_background.png"; - background_data.left = 16; - background_data.top = 16; - background_data.right = 16; - background_data.bottom = 16; - - if (!m_collapse_toolbar.init(background_data)) - { - // unable to init the toolbar texture, disable it - m_collapse_toolbar.set_enabled(false); - return true; - } - - m_collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); - m_collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); - m_collapse_toolbar.set_border(5.0f); - m_collapse_toolbar.set_separator_size(5); - m_collapse_toolbar.set_gap_size(2); - - GLToolbarItem::Data item; - - item.name = "collapse_sidebar"; - item.icon_filename = "collapse.svg"; - item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel")); - item.sprite_id = 0; - item.left.action_callback = [this, item]() { - std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? - _utf8(L("Collapse right panel")) : _utf8(L("Expand right panel")); - - int id = m_collapse_toolbar.get_item_id("collapse_sidebar"); - m_collapse_toolbar.set_tooltip(id, new_tooltip); - set_tooltip(""); - - wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); - }; - - if (!m_collapse_toolbar.add_item(item)) - return false; - - return true; + return wxGetApp().plater()->init_collapse_toolbar(); } bool GLCanvas3D::_set_current() @@ -5567,20 +5517,21 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const float size = GLToolbar::Default_Icons_Size * scale; // Set current size for all top toolbars. It will be used for next calculations + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); #if ENABLE_RETINA_GL const float sc = m_retina_helper->get_scale_factor() * scale; m_main_toolbar.set_scale(sc); m_undoredo_toolbar.set_scale(sc); - m_collapse_toolbar.set_scale(sc); + collapse_toolbar.set_scale(sc); size *= m_retina_helper->get_scale_factor(); #else m_main_toolbar.set_icons_size(size); m_undoredo_toolbar.set_icons_size(size); - m_collapse_toolbar.set_icons_size(size); + collapse_toolbar.set_icons_size(size); #endif // ENABLE_RETINA_GL - float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + m_collapse_toolbar.get_width(); - int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + m_collapse_toolbar.get_visible_items_cnt(); + float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); + int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars // calculate scale needed for items in all top toolbars @@ -5600,7 +5551,6 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const wxGetApp().set_auto_toolbar_icon_scale(new_scale); } - void GLCanvas3D::_render_overlays() const { glsafe(::glDisable(GL_DEPTH_TEST)); @@ -5627,12 +5577,12 @@ void GLCanvas3D::_render_overlays() const const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); m_main_toolbar.set_scale(scale); m_undoredo_toolbar.set_scale(scale); - m_collapse_toolbar.set_scale(scale); + wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); #else const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); m_main_toolbar.set_icons_size(size); m_undoredo_toolbar.set_icons_size(size); - m_collapse_toolbar.set_icons_size(size); + wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); #endif // ENABLE_RETINA_GL _render_main_toolbar(); @@ -5738,7 +5688,8 @@ void GLCanvas3D::_render_main_toolbar() const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f; + const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; m_main_toolbar.set_position(top, left); @@ -5754,7 +5705,8 @@ void GLCanvas3D::_render_undoredo_toolbar() const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f; + const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; m_undoredo_toolbar.set_position(top, left); m_undoredo_toolbar.render(*this); @@ -5762,8 +5714,7 @@ void GLCanvas3D::_render_undoredo_toolbar() const void GLCanvas3D::_render_collapse_toolbar() const { - if (!m_collapse_toolbar.is_enabled()) - return; + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); Size cnv_size = get_canvas_size(); float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); @@ -5771,10 +5722,10 @@ void GLCanvas3D::_render_collapse_toolbar() const float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float left = (0.5f * (float)cnv_size.get_width() - (float)m_collapse_toolbar.get_width() - band) * inv_zoom; + float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; - m_collapse_toolbar.set_position(top, left); - m_collapse_toolbar.render(*this); + collapse_toolbar.set_position(top, left); + collapse_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const @@ -7342,9 +7293,10 @@ bool GLCanvas3D::_activate_search_toolbar_item() bool GLCanvas3D::_deactivate_collapse_toolbar_items() { - if (m_collapse_toolbar.is_item_pressed("print")) + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + if (collapse_toolbar.is_item_pressed("print")) { - m_collapse_toolbar.force_left_action(m_collapse_toolbar.get_item_id("print"), *this); + collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this); return true; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index bba87d07a..810a99f21 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -127,6 +127,9 @@ class GLCanvas3D static const double DefaultCameraZoomToBoxMarginFactor; public: +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ struct GCodePreviewVolumeIndex { enum EType @@ -153,6 +156,9 @@ public: void reset() { first_volumes.clear(); } }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ private: class LayersEditing @@ -464,7 +470,6 @@ private: mutable GLGizmosManager m_gizmos; mutable GLToolbar m_main_toolbar; mutable GLToolbar m_undoredo_toolbar; - mutable GLToolbar m_collapse_toolbar; ClippingPlane m_clipping_planes[2]; mutable ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; @@ -509,7 +514,13 @@ private: bool m_reload_delayed; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GCodePreviewVolumeIndex m_gcode_preview_volume_index; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_RENDER_PICKING_PASS bool m_show_picking_texture; @@ -611,7 +622,6 @@ public: void enable_selection(bool enable); void enable_main_toolbar(bool enable); void enable_undoredo_toolbar(bool enable); - void enable_collapse_toolbar(bool enable); void enable_dynamic_background(bool enable); void enable_labels(bool enable) { m_labels.enable(enable); } #if ENABLE_SLOPE_RENDERING diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 59401b11a..4ab282b06 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -1238,7 +1238,7 @@ bool GLToolbar::generate_icons_texture() const } std::vector> states; - if (m_name == "Top") + if (m_type == Normal) { states.push_back({ 1, false }); // Normal states.push_back({ 0, false }); // Pressed @@ -1247,7 +1247,7 @@ bool GLToolbar::generate_icons_texture() const states.push_back({ 0, false }); // HoverPressed states.push_back({ 2, false }); // HoverDisabled } - else if (m_name == "View") + else { states.push_back({ 1, false }); // Normal states.push_back({ 1, true }); // Pressed diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9dfb4b422..ad2658298 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -760,6 +760,22 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const dialog.GetPaths(input_files); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const +{ + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _(L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):")), + app_config->get_last_dir(), "", + file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + bool GUI_App::switch_language() { if (select_language()) { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 4ddaef844..11f3a7c3c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -156,6 +156,12 @@ public: void keyboard_shortcuts(); void load_project(wxWindow *parent, wxString& input_file) const; void import_model(wxWindow *parent, wxArrayString& input_files) const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + void load_gcode(wxWindow* parent, wxString& input_file) const; +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + static bool catch_error(std::function cb, const std::string& err); void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 65791150a..6b3866ca4 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -13,6 +13,11 @@ #include "PresetBundle.hpp" #include "DoubleSlider.hpp" #include "Plater.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +#include "MainFrame.hpp" +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include #include @@ -70,7 +75,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba m_canvas->enable_selection(true); m_canvas->enable_main_toolbar(true); m_canvas->enable_undoredo_toolbar(true); - m_canvas->enable_collapse_toolbar(true); m_canvas->enable_labels(true); #if ENABLE_SLOPE_RENDERING m_canvas->enable_slope(true); @@ -248,7 +252,6 @@ bool Preview::init(wxWindow* parent, Model* model) m_canvas->set_process(m_process); m_canvas->enable_legend_texture(true); m_canvas->enable_dynamic_background(true); - m_canvas->enable_collapse_toolbar(true); #if ENABLE_GCODE_VIEWER m_layers_slider_sizer = create_layers_slider_sizer(); @@ -1190,6 +1193,14 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent & event) void Preview::load_print_as_fff(bool keep_z_range) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + if (wxGetApp().mainframe == nullptr) + // avoid proessing while mainframe is being constructed + return; +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (m_loaded || m_process->current_printer_technology() != ptFFF) return; @@ -1213,10 +1224,23 @@ void Preview::load_print_as_fff(bool keep_z_range) } } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer && !has_layers) +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (! has_layers) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { #if ENABLE_GCODE_VIEWER hide_layers_slider(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + GetSizer()->Hide(m_bottom_toolbar_panel); + GetSizer()->Layout(); + Refresh(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else reset_sliders(true); m_canvas->reset_legend_texture(); @@ -1246,7 +1270,15 @@ void Preview::load_print_as_fff(bool keep_z_range) #if ENABLE_GCODE_VIEWER GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool gcode_preview_data_valid = print->is_step_done(psGCodeExport); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else bool gcode_preview_data_valid = print->is_step_done(psGCodeExport) && ! m_gcode_preview_data->empty(); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index cc599387d..89be99e06 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -81,7 +81,29 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // initialize tabpanel and menubar init_tabpanel(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + init_editor_menubar(); + init_gcodeviewer_menubar(); + +#if _WIN32 + // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad + wxAcceleratorEntry entries[6]; + entries[0].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1); + entries[1].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2); + entries[2].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3); + entries[3].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4); + entries[4].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5); + entries[5].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6); + wxAcceleratorTable accel(6, entries); + SetAcceleratorTable(accel); +#endif // _WIN32 +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ init_menubar(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // set default tooltip timer in msec // SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values @@ -226,6 +248,22 @@ void MainFrame::shutdown() } #endif // _WIN32 +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + if (m_plater != nullptr) { + m_plater->stop_jobs(); + + // Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC, + // when closing the application using Command+Q, a mouse event is triggered after this lambda is completed, + // causing a crash + m_plater->unbind_canvas_event_handlers(); + + // Cleanup of canvases' volumes needs to be done here or a crash may happen on some Linux Debian flavours + // see: https://github.com/prusa3d/PrusaSlicer/issues/3964 + m_plater->reset_canvas_volumes(); + } +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_plater) m_plater->stop_jobs(); @@ -237,6 +275,9 @@ void MainFrame::shutdown() // Cleanup of canvases' volumes needs to be done here or a crash may happen on some Linux Debian flavours // see: https://github.com/prusa3d/PrusaSlicer/issues/3964 if (m_plater) m_plater->reset_canvas_volumes(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Weird things happen as the Paint messages are floating around the windows being destructed. // Avoid the Paint messages by hiding the main window. @@ -247,11 +288,19 @@ void MainFrame::shutdown() if (m_settings_dialog) m_settings_dialog->Destroy(); - // Stop the background thread (Windows and Linux). - // Disconnect from a 3DConnextion driver (OSX). - m_plater->get_mouse3d_controller().shutdown(); - // Store the device parameter database back to appconfig. - m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (m_plater != nullptr) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (m_restore_from_gcode_viewer.collapsed_sidebar) + m_plater->collapse_sidebar(false); + // Stop the background thread (Windows and Linux). + // Disconnect from a 3DConnextion driver (OSX). + m_plater->get_mouse3d_controller().shutdown(); + // Store the device parameter database back to appconfig. + m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater. wxGetApp().removable_drive_manager()->shutdown(); @@ -593,7 +642,85 @@ void MainFrame::on_sys_color_changed() msw_rescale_menu(menu_bar->GetMenu(id)); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +#ifdef _MSC_VER + // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators, + // as the simple numeric accelerators spoil all numeric data entry. +static const wxString sep = "\t\xA0"; +static const wxString sep_space = "\xA0"; +#else +static const wxString sep = " - "; +static const wxString sep_space = ""; +#endif + +static wxMenu* generate_help_menu() +{ + wxMenu* helpMenu = new wxMenu(); + append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"), + [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); + append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"), + [](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/PrusaSlicer/releases"); }); +//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ +//# wxTheApp->check_version(1); +//# }); +//# $versioncheck->Enable(wxTheApp->have_version_check); + append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Website"), SLIC3R_APP_NAME), + wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME), + [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); }); +// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("%s &Manual")), SLIC3R_APP_NAME), +// wxString::Format(_(L("Open the %s manual in your browser")), SLIC3R_APP_NAME), +// [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); }); + helpMenu->AppendSeparator(); + append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"), + [](wxCommandEvent&) { wxGetApp().system_info(); }); + append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"), + [](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); + append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME), + [](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); }); + append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), + [](wxCommandEvent&) { Slic3r::GUI::about(); }); + helpMenu->AppendSeparator(); + append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"), + [](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG + helpMenu->AppendSeparator(); + append_menu_item(helpMenu, wxID_ANY, "DEBUG gcode thumbnails", "DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails", + [](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); }); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + + return helpMenu; +} + +static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, std::function can_change_view) +{ + // The camera control accelerators are captured by GLCanvas3D::on_char(). + append_menu_item(view_menu, wxID_ANY, _L("Iso") + sep + "&0", _L("Iso View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("iso"); }, + "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); + view_menu->AppendSeparator(); + //TRN To be shown in the main menu View->Top + append_menu_item(view_menu, wxID_ANY, _L("Top") + sep + "&1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); }, + "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); + //TRN To be shown in the main menu View->Bottom + append_menu_item(view_menu, wxID_ANY, _L("Bottom") + sep + "&2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); }, + "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); + append_menu_item(view_menu, wxID_ANY, _L("Front") + sep + "&3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); }, + "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); + append_menu_item(view_menu, wxID_ANY, _L("Rear") + sep + "&4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); }, + "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); + append_menu_item(view_menu, wxID_ANY, _L("Left") + sep + "&5", _L("Left View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("left"); }, + "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); + append_menu_item(view_menu, wxID_ANY, _L("Right") + sep + "&6", _L("Right View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("right"); }, + "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); +} + +void MainFrame::init_editor_menubar() +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void MainFrame::init_menubar() +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { #ifdef __APPLE__ wxMenuBar::SetAutoWindowMenu(false); @@ -602,15 +729,15 @@ void MainFrame::init_menubar() // File menu wxMenu* fileMenu = new wxMenu; { - append_menu_item(fileMenu, wxID_ANY, _(L("&New Project")) + "\tCtrl+N", _(L("Start a new project")), + append_menu_item(fileMenu, wxID_ANY, _L("&New Project") + "\tCtrl+N", _L("Start a new project"), [this](wxCommandEvent&) { if (m_plater) m_plater->new_project(); }, "", nullptr, [this](){return m_plater != nullptr && can_start_new_project(); }, this); - append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")), + append_menu_item(fileMenu, wxID_ANY, _L("&Open Project") + dots + "\tCtrl+O", _L("Open a project file"), [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "open", nullptr, [this](){return m_plater != nullptr; }, this); wxMenu* recent_projects_menu = new wxMenu(); - wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _(L("Recent projects")), ""); + wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _L("Recent projects"), ""); m_recent_projects.UseMenu(recent_projects_menu); Bind(wxEVT_MENU, [this](wxCommandEvent& evt) { size_t file_id = evt.GetId() - wxID_FILE1; @@ -619,7 +746,7 @@ void MainFrame::init_menubar() m_plater->load_project(filename); else { - wxMessageDialog msg(this, _(L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?")), _(L("Error")), wxYES_NO | wxYES_DEFAULT); + wxMessageDialog msg(this, _L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?"), _L("Error"), wxYES_NO | wxYES_DEFAULT); if (msg.ShowModal() == wxID_YES) { m_recent_projects.RemoveFileFromHistory(file_id); @@ -644,13 +771,13 @@ void MainFrame::init_menubar() Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); - append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")), + append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); #ifdef __APPLE__ - append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Shift+S", _(L("Save current project file as")), + append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"), #else - append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Alt+S", _(L("Save current project file as")), + append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"), #endif // __APPLE__ [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); @@ -658,7 +785,7 @@ void MainFrame::init_menubar() fileMenu->AppendSeparator(); wxMenu* import_menu = new wxMenu(); - append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")), + append_menu_item(import_menu, wxID_ANY, _L("Import STL/OBJ/AM&F/3MF") + dots + "\tCtrl+I", _L("Load a model"), [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); @@ -666,59 +793,59 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); - append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")), + append_menu_item(import_menu, wxID_ANY, _L("Import SL1 archive") + dots, _L("Load an SL1 output archive"), [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); import_menu->AppendSeparator(); - append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")), + append_menu_item(import_menu, wxID_ANY, _L("Import &Config") + dots + "\tCtrl+L", _L("Load exported configuration file"), [this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr, [this]() {return true; }, this); - append_menu_item(import_menu, wxID_ANY, _(L("Import Config from &project")) + dots +"\tCtrl+Alt+L", _(L("Load configuration from project file")), + append_menu_item(import_menu, wxID_ANY, _L("Import Config from &project") + dots +"\tCtrl+Alt+L", _L("Load configuration from project file"), [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "import_config", nullptr, [this]() {return true; }, this); import_menu->AppendSeparator(); - append_menu_item(import_menu, wxID_ANY, _(L("Import Config &Bundle")) + dots, _(L("Load presets from a bundle")), + append_menu_item(import_menu, wxID_ANY, _L("Import Config &Bundle") + dots, _L("Load presets from a bundle"), [this](wxCommandEvent&) { load_configbundle(); }, "import_config_bundle", nullptr, [this]() {return true; }, this); - append_submenu(fileMenu, import_menu, wxID_ANY, _(L("&Import")), ""); + append_submenu(fileMenu, import_menu, wxID_ANY, _L("&Import"), ""); wxMenu* export_menu = new wxMenu(); - wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")), + wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _L("Export &G-code") + dots +"\tCtrl+G", _L("Export current plate as G-code"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode", nullptr, [this](){return can_export_gcode(); }, this); m_changeable_menu_items.push_back(item_export_gcode); - wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")), + wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _L("S&end G-code") + dots +"\tCtrl+Shift+G", _L("Send to print current plate as G-code"), [this](wxCommandEvent&) { if (m_plater) m_plater->send_gcode(); }, "export_gcode", nullptr, [this](){return can_send_gcode(); }, this); m_changeable_menu_items.push_back(item_send_gcode); - append_menu_item(export_menu, wxID_ANY, _(L("Export G-code to SD card / Flash drive")) + dots + "\tCtrl+U", _(L("Export current plate as G-code to SD card / Flash drive")), + append_menu_item(export_menu, wxID_ANY, _L("Export G-code to SD card / Flash drive") + dots + "\tCtrl+U", _L("Export current plate as G-code to SD card / Flash drive"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(true); }, "export_to_sd", nullptr, [this]() {return can_export_gcode_sd(); }, this); export_menu->AppendSeparator(); - append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")), + append_menu_item(export_menu, wxID_ANY, _L("Export plate as &STL") + dots, _L("Export current plate as STL"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater", nullptr, [this](){return can_export_model(); }, this); - append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL &including supports")) + dots, _(L("Export current plate as STL including supports")), + append_menu_item(export_menu, wxID_ANY, _L("Export plate as STL &including supports") + dots, _L("Export current plate as STL including supports"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, "export_plater", nullptr, [this](){return can_export_supports(); }, this); - append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")), + append_menu_item(export_menu, wxID_ANY, _L("Export plate as &AMF") + dots, _L("Export current plate as AMF"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater", nullptr, [this](){return can_export_model(); }, this); export_menu->AppendSeparator(); - append_menu_item(export_menu, wxID_ANY, _(L("Export &toolpaths as OBJ")) + dots, _(L("Export toolpaths as OBJ")), + append_menu_item(export_menu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, [this]() {return can_export_toolpaths(); }, this); export_menu->AppendSeparator(); - append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")), + append_menu_item(export_menu, wxID_ANY, _L("Export &Config") + dots +"\tCtrl+E", _L("Export current configuration to file"), [this](wxCommandEvent&) { export_config(); }, "export_config", nullptr, [this]() {return true; }, this); - append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")), + append_menu_item(export_menu, wxID_ANY, _L("Export Config &Bundle") + dots, _L("Export all presets to file"), [this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle", nullptr, [this]() {return true; }, this); - append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), ""); + append_submenu(fileMenu, export_menu, wxID_ANY, _L("&Export"), ""); - append_menu_item(fileMenu, wxID_ANY, _(L("Ejec&t SD card / Flash drive")) + dots + "\tCtrl+T", _(L("Eject SD card / Flash drive after the G-code was exported to it.")), + append_menu_item(fileMenu, wxID_ANY, _L("Ejec&t SD card / Flash drive") + dots + "\tCtrl+T", _L("Eject SD card / Flash drive after the G-code was exported to it."), [this](wxCommandEvent&) { if (m_plater) m_plater->eject_drive(); }, "eject_sd", nullptr, [this]() {return can_eject(); }, this); @@ -726,19 +853,19 @@ void MainFrame::init_menubar() #if 0 m_menu_item_repeat = nullptr; - append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice")) +dots+ "\tCtrl+U", _(L("Slice a file into a G-code")), + append_menu_item(fileMenu, wxID_ANY, _L("Quick Slice") +dots+ "\tCtrl+U", _L("Slice a file into a G-code"), [this](wxCommandEvent&) { wxTheApp->CallAfter([this]() { quick_slice(); m_menu_item_repeat->Enable(is_last_input_file()); }); }, "cog_go.png"); - append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice and Save As")) +dots +"\tCtrl+Alt+U", _(L("Slice a file into a G-code, save as")), + append_menu_item(fileMenu, wxID_ANY, _L("Quick Slice and Save As") +dots +"\tCtrl+Alt+U", _L("Slice a file into a G-code, save as"), [this](wxCommandEvent&) { wxTheApp->CallAfter([this]() { quick_slice(qsSaveAs); m_menu_item_repeat->Enable(is_last_input_file()); }); }, "cog_go.png"); - m_menu_item_repeat = append_menu_item(fileMenu, wxID_ANY, _(L("Repeat Last Quick Slice")) +"\tCtrl+Shift+U", _(L("Repeat last quick slice")), + m_menu_item_repeat = append_menu_item(fileMenu, wxID_ANY, _L("Repeat Last Quick Slice") +"\tCtrl+Shift+U", _L("Repeat last quick slice"), [this](wxCommandEvent&) { wxTheApp->CallAfter([this]() { quick_slice(qsReslice); @@ -746,18 +873,28 @@ void MainFrame::init_menubar() m_menu_item_repeat->Enable(false); fileMenu->AppendSeparator(); #endif - m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(Re)Slice No&w")) + "\tCtrl+R", _(L("Start new slicing process")), + m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _L("(Re)Slice No&w") + "\tCtrl+R", _L("Start new slicing process"), [this](wxCommandEvent&) { reslice_now(); }, "re_slice", nullptr, [this](){return m_plater != nullptr && can_reslice(); }, this); fileMenu->AppendSeparator(); - append_menu_item(fileMenu, wxID_ANY, _(L("&Repair STL file")) + dots, _(L("Automatically repair an STL file")), + append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() {return true; }, this); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE fileMenu->AppendSeparator(); - append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), wxString::Format(_(L("Quit %s")), SLIC3R_APP_NAME), + append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), + [this](wxCommandEvent&) { set_mode(EMode::GCodeViewer); }); +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + fileMenu->AppendSeparator(); + append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #ifdef _MSC_VER // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators, // as the simple numeric accelerators spoil all numeric data entry. @@ -767,6 +904,9 @@ void MainFrame::init_menubar() wxString sep = " - "; wxString sep_space = ""; #endif +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Edit menu wxMenu* editMenu = nullptr; @@ -779,44 +919,44 @@ void MainFrame::init_menubar() #else wxString hotkey_delete = "Del"; #endif - append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "A", - _(L("Selects all objects")), [this](wxCommandEvent&) { m_plater->select_all(); }, + append_menu_item(editMenu, wxID_ANY, _L("&Select all") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "A", + _L("Selects all objects"), [this](wxCommandEvent&) { m_plater->select_all(); }, "", nullptr, [this](){return can_select(); }, this); - append_menu_item(editMenu, wxID_ANY, _(L("D&eselect all")) + sep + "Esc", - _(L("Deselects all objects")), [this](wxCommandEvent&) { m_plater->deselect_all(); }, + append_menu_item(editMenu, wxID_ANY, _L("D&eselect all") + sep + "Esc", + _L("Deselects all objects"), [this](wxCommandEvent&) { m_plater->deselect_all(); }, "", nullptr, [this](){return can_deselect(); }, this); editMenu->AppendSeparator(); - append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete, - _(L("Deletes the current selection")),[this](wxCommandEvent&) { m_plater->remove_selected(); }, + append_menu_item(editMenu, wxID_ANY, _L("&Delete selected") + sep + hotkey_delete, + _L("Deletes the current selection"),[this](wxCommandEvent&) { m_plater->remove_selected(); }, "remove_menu", nullptr, [this](){return can_delete(); }, this); - append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete, - _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); }, + append_menu_item(editMenu, wxID_ANY, _L("Delete &all") + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete, + _L("Deletes all objects"), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); }, "delete_all_menu", nullptr, [this](){return can_delete_all(); }, this); editMenu->AppendSeparator(); - append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z", - _(L("Undo")), [this](wxCommandEvent&) { m_plater->undo(); }, + append_menu_item(editMenu, wxID_ANY, _L("&Undo") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z", + _L("Undo"), [this](wxCommandEvent&) { m_plater->undo(); }, "undo_menu", nullptr, [this](){return m_plater->can_undo(); }, this); - append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y", - _(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); }, + append_menu_item(editMenu, wxID_ANY, _L("&Redo") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y", + _L("Redo"), [this](wxCommandEvent&) { m_plater->redo(); }, "redo_menu", nullptr, [this](){return m_plater->can_redo(); }, this); editMenu->AppendSeparator(); - append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", - _(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, + append_menu_item(editMenu, wxID_ANY, _L("&Copy") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", + _L("Copy selection to clipboard"), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, "copy_menu", nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this); - append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V", - _(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, + append_menu_item(editMenu, wxID_ANY, _L("&Paste") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V", + _L("Paste clipboard"), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, "paste_menu", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); editMenu->AppendSeparator(); - append_menu_item(editMenu, wxID_ANY, _(L("Re&load from disk")) + sep + "F5", - _(L("Reload the plater from disk")), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); }, + append_menu_item(editMenu, wxID_ANY, _L("Re&load from disk") + sep + "F5", + _L("Reload the plater from disk"), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); }, "", nullptr, [this]() {return !m_plater->model().objects.empty(); }, this); editMenu->AppendSeparator(); - append_menu_item(editMenu, wxID_ANY, _(L("Searc&h")) + "\tCtrl+F", - _(L("Find option")), [this](wxCommandEvent&) { m_plater->search(/*m_tabpanel->GetCurrentPage() == */m_plater->IsShown()); }, + append_menu_item(editMenu, wxID_ANY, _L("Searc&h") + "\tCtrl+F", + _L("Find option"), [this](wxCommandEvent&) { m_plater->search(/*m_tabpanel->GetCurrentPage() == */m_plater->IsShown()); }, "search", nullptr, [this]() {return true; }, this); } @@ -824,32 +964,35 @@ void MainFrame::init_menubar() auto windowMenu = new wxMenu(); { if (m_plater) { - append_menu_item(windowMenu, wxID_HIGHEST + 1, _(L("&Plater Tab")) + "\tCtrl+1", _(L("Show the plater")), + append_menu_item(windowMenu, wxID_HIGHEST + 1, _L("&Plater Tab") + "\tCtrl+1", _L("Show the plater"), [this](wxCommandEvent&) { select_tab(0); }, "plater", nullptr, [this]() {return true; }, this); windowMenu->AppendSeparator(); } - append_menu_item(windowMenu, wxID_HIGHEST + 2, _(L("P&rint Settings Tab")) + "\tCtrl+2", _(L("Show the print settings")), + append_menu_item(windowMenu, wxID_HIGHEST + 2, _L("P&rint Settings Tab") + "\tCtrl+2", _L("Show the print settings"), [this/*, tab_offset*/](wxCommandEvent&) { select_tab(1); }, "cog", nullptr, [this]() {return true; }, this); - wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _(L("&Filament Settings Tab")) + "\tCtrl+3", _(L("Show the filament settings")), + wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _L("&Filament Settings Tab") + "\tCtrl+3", _L("Show the filament settings"), [this/*, tab_offset*/](wxCommandEvent&) { select_tab(2); }, "spool", nullptr, [this]() {return true; }, this); m_changeable_menu_items.push_back(item_material_tab); - wxMenuItem* item_printer_tab = append_menu_item(windowMenu, wxID_HIGHEST + 4, _(L("Print&er Settings Tab")) + "\tCtrl+4", _(L("Show the printer settings")), + wxMenuItem* item_printer_tab = append_menu_item(windowMenu, wxID_HIGHEST + 4, _L("Print&er Settings Tab") + "\tCtrl+4", _L("Show the printer settings"), [this/*, tab_offset*/](wxCommandEvent&) { select_tab(3); }, "printer", nullptr, [this]() {return true; }, this); m_changeable_menu_items.push_back(item_printer_tab); if (m_plater) { windowMenu->AppendSeparator(); - append_menu_item(windowMenu, wxID_HIGHEST + 5, _(L("3&D")) + "\tCtrl+5", _(L("Show the 3D editing view")), + append_menu_item(windowMenu, wxID_HIGHEST + 5, _L("3&D") + "\tCtrl+5", _L("Show the 3D editing view"), [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "editor_menu", nullptr, [this](){return can_change_view(); }, this); - append_menu_item(windowMenu, wxID_HIGHEST + 6, _(L("Pre&view")) + "\tCtrl+6", _(L("Show the 3D slices preview")), + append_menu_item(windowMenu, wxID_HIGHEST + 6, _L("Pre&view") + "\tCtrl+6", _L("Show the 3D slices preview"), [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "preview_menu", nullptr, [this](){return can_change_view(); }, this); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if _WIN32 // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad wxAcceleratorEntry entries[6]; @@ -862,9 +1005,12 @@ void MainFrame::init_menubar() wxAcceleratorTable accel(6, entries); SetAcceleratorTable(accel); #endif // _WIN32 +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ windowMenu->AppendSeparator(); - append_menu_item(windowMenu, wxID_ANY, _(L("Print &Host Upload Queue")) + "\tCtrl+J", _(L("Display the Print Host Upload Queue window")), + append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"), [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, [this]() {return true; }, this); } @@ -873,72 +1019,85 @@ void MainFrame::init_menubar() wxMenu* viewMenu = nullptr; if (m_plater) { viewMenu = new wxMenu(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this)); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // The camera control accelerators are captured by GLCanvas3D::on_char(). - append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + sep + "&0", _(L("Iso View")),[this](wxCommandEvent&) { select_view("iso"); }, + append_menu_item(viewMenu, wxID_ANY, _L("Iso") + sep + "&0", _L("Iso View"), [this](wxCommandEvent&) { select_view("iso"); }, "", nullptr, [this](){return can_change_view(); }, this); viewMenu->AppendSeparator(); //TRN To be shown in the main menu View->Top - append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + sep + "&1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); }, + append_menu_item(viewMenu, wxID_ANY, _L("Top") + sep + "&1", _L("Top View"), [this](wxCommandEvent&) { select_view("top"); }, "", nullptr, [this](){return can_change_view(); }, this); //TRN To be shown in the main menu View->Bottom - append_menu_item(viewMenu, wxID_ANY, _(L("Bottom")) + sep + "&2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); }, + append_menu_item(viewMenu, wxID_ANY, _L("Bottom") + sep + "&2", _L("Bottom View"), [this](wxCommandEvent&) { select_view("bottom"); }, "", nullptr, [this](){return can_change_view(); }, this); - append_menu_item(viewMenu, wxID_ANY, _(L("Front")) + sep + "&3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); }, + append_menu_item(viewMenu, wxID_ANY, _L("Front") + sep + "&3", _L("Front View"), [this](wxCommandEvent&) { select_view("front"); }, "", nullptr, [this](){return can_change_view(); }, this); - append_menu_item(viewMenu, wxID_ANY, _(L("Rear")) + sep + "&4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); }, + append_menu_item(viewMenu, wxID_ANY, _L("Rear") + sep + "&4", _L("Rear View"), [this](wxCommandEvent&) { select_view("rear"); }, "", nullptr, [this](){return can_change_view(); }, this); - append_menu_item(viewMenu, wxID_ANY, _(L("Left")) + sep + "&5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); }, + append_menu_item(viewMenu, wxID_ANY, _L("Left") + sep + "&5", _L("Left View"), [this](wxCommandEvent&) { select_view("left"); }, "", nullptr, [this](){return can_change_view(); }, this); - append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + sep + "&6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); }, + append_menu_item(viewMenu, wxID_ANY, _L("Right") + sep + "&6", _L("Right View"), [this](wxCommandEvent&) { select_view("right"); }, "", nullptr, [this](){return can_change_view(); }, this); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ viewMenu->AppendSeparator(); #if ENABLE_SLOPE_RENDERING wxMenu* options_menu = new wxMenu(); - append_menu_check_item(options_menu, wxID_ANY, _(L("Show &labels")) + sep + "E", _(L("Show object/instance labels in 3D scene")), + append_menu_check_item(options_menu, wxID_ANY, _L("Show &labels") + sep + "E", _L("Show object/instance labels in 3D scene"), [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this); - append_menu_check_item(options_menu, wxID_ANY, _(L("Show &slope")) + sep + "D", _(L("Objects coloring using faces' slope")), + append_menu_check_item(options_menu, wxID_ANY, _L("Show &slope") + sep + "D", _L("Objects coloring using faces' slope"), [this](wxCommandEvent&) { m_plater->show_view3D_slope(!m_plater->is_view3D_slope_shown()); }, this, [this]() { return m_plater->is_view3D_shown() && !m_plater->is_view3D_layers_editing_enabled(); }, [this]() { return m_plater->is_view3D_slope_shown(); }, this); - append_submenu(viewMenu, options_menu, wxID_ANY, _(L("&Options")), ""); + append_submenu(viewMenu, options_menu, wxID_ANY, _L("&Options"), ""); #else - append_menu_check_item(viewMenu, wxID_ANY, _(L("Show &labels")) + sep + "E", _(L("Show object/instance labels in 3D scene")), + append_menu_check_item(viewMenu, wxID_ANY, _L("Show &labels") + sep + "E", _L("Show object/instance labels in 3D scene"), [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this); #endif // ENABLE_SLOPE_RENDERING - append_menu_check_item(viewMenu, wxID_ANY, _(L("&Collapse sidebar")), _(L("Collapse sidebar")), + append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse sidebar"), _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, [this]() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); } // Help menu +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + auto helpMenu = generate_help_menu(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto helpMenu = new wxMenu(); { - append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D &Drivers")), _(L("Open the Prusa3D drivers download page in your browser")), + append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"), [this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); - append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")), + append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"), [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/PrusaSlicer/releases"); }); //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# wxTheApp->check_version(1); //# }); //# $versioncheck->Enable(wxTheApp->have_version_check); - append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("%s &Website")), SLIC3R_APP_NAME), - wxString::Format(_(L("Open the %s website in your browser")), SLIC3R_APP_NAME), + append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Website"), SLIC3R_APP_NAME), + wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); }); // append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("%s &Manual")), SLIC3R_APP_NAME), // wxString::Format(_(L("Open the %s manual in your browser")), SLIC3R_APP_NAME), // [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); }); helpMenu->AppendSeparator(); - append_menu_item(helpMenu, wxID_ANY, _(L("System &Info")), _(L("Show system information")), + append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"), [this](wxCommandEvent&) { wxGetApp().system_info(); }); - append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")), + append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"), [this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); - append_menu_item(helpMenu, wxID_ANY, _(L("Report an I&ssue")), wxString::Format(_(L("Report an issue on %s")), SLIC3R_APP_NAME), + append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); }); - append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("&About %s")), SLIC3R_APP_NAME), _(L("Show about dialog")), + append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), [this](wxCommandEvent&) { Slic3r::GUI::about(); }); helpMenu->AppendSeparator(); - append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")), + append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"), [this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); #if ENABLE_THUMBNAIL_GENERATOR_DEBUG helpMenu->AppendSeparator(); @@ -946,10 +1105,26 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); }); #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // menubar // assign menubar to frame after appending items, otherwise special items // will not be handled correctly +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + m_editor_menubar = new wxMenuBar(); + m_editor_menubar->Append(fileMenu, _L("&File")); + if (editMenu) m_editor_menubar->Append(editMenu, _L("&Edit")); + m_editor_menubar->Append(windowMenu, _L("&Window")); + if (viewMenu) m_editor_menubar->Append(viewMenu, _L("&View")); + // Add additional menus from C++ + wxGetApp().add_config_menu(m_editor_menubar); + m_editor_menubar->Append(helpMenu, _L("&Help")); + SetMenuBar(m_editor_menubar); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto menubar = new wxMenuBar(); menubar->Append(fileMenu, _(L("&File"))); if (editMenu) menubar->Append(editMenu, _(L("&Edit"))); @@ -959,6 +1134,9 @@ void MainFrame::init_menubar() wxGetApp().add_config_menu(menubar); menubar->Append(helpMenu, _(L("&Help"))); SetMenuBar(menubar); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #ifdef __APPLE__ // This fixes a bug on Mac OS where the quit command doesn't emit window close events @@ -972,10 +1150,125 @@ void MainFrame::init_menubar() #endif if (plater()->printer_technology() == ptSLA) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + update_editor_menubar(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ update_menubar(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +void MainFrame::init_gcodeviewer_menubar() +{ + wxMenu* fileMenu = new wxMenu; + { + append_menu_item(fileMenu, wxID_ANY, _L("&Open G-code") + dots + "\tCtrl+O", _L("Open a G-code file"), + [this](wxCommandEvent&) { if (m_plater) m_plater->load_gcode(); }, "open", nullptr, + [this]() {return m_plater != nullptr; }, this); + fileMenu->AppendSeparator(); + append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"), + [this](wxCommandEvent&) { set_mode(EMode::Editor); }); + fileMenu->AppendSeparator(); + append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), + [this](wxCommandEvent&) { Close(false); }); + } + + // View menu + wxMenu* viewMenu = nullptr; + if (m_plater != nullptr) { + viewMenu = new wxMenu(); + add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this)); + } + + // helpmenu + auto helpMenu = generate_help_menu(); + + m_gcodeviewer_menubar = new wxMenuBar(); + m_gcodeviewer_menubar->Append(fileMenu, _L("&File")); + if ((viewMenu != nullptr)) + m_gcodeviewer_menubar->Append(viewMenu, _L("&View")); + m_gcodeviewer_menubar->Append(helpMenu, _L("&Help")); +} + +void MainFrame::set_mode(EMode mode) +{ + m_mode = mode; + switch (m_mode) + { + default: + case EMode::Editor: + { + m_plater->reset(); + + // switch view + m_plater->select_view_3D("3D"); + // switch menubar + SetMenuBar(m_editor_menubar); + + // show toolbars + m_plater->enable_view_toolbar(true); + + if (m_restore_from_gcode_viewer.collapse_toolbar_enabled) { + m_plater->get_collapse_toolbar().set_enabled(true); + m_restore_from_gcode_viewer.collapse_toolbar_enabled = false; + } + + // show sidebar + if (m_restore_from_gcode_viewer.collapsed_sidebar) { + m_plater->collapse_sidebar(false); + m_restore_from_gcode_viewer.collapsed_sidebar = false; + } + + break; + } + case EMode::GCodeViewer: + { + m_plater->reset(); + + // reinitialize undo/redo stack + m_plater->clear_undo_redo_stack_main(); + m_plater->take_snapshot(_L("New Project")); + + // switch view + m_plater->select_view_3D("Preview"); + // switch menubar + SetMenuBar(m_gcodeviewer_menubar); + + // hide toolbars + m_plater->enable_view_toolbar(false); + + if (wxGetApp().app_config->get("show_collapse_button") == "1") { + m_plater->get_collapse_toolbar().set_enabled(false); + m_restore_from_gcode_viewer.collapse_toolbar_enabled = true; + } + + // hide sidebar + if (wxGetApp().app_config->get("collapsed_sidebar") != "1") { + m_plater->collapse_sidebar(true); + m_restore_from_gcode_viewer.collapsed_sidebar = true; + } + + break; + } + } +} +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +void MainFrame::update_editor_menubar() +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void MainFrame::update_menubar() +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { const bool is_fff = plater()->printer_technology() == ptFFF; diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 3b64be9bc..8a68fa36f 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -68,6 +68,21 @@ class MainFrame : public DPIFrame wxString m_qs_last_input_file = wxEmptyString; wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + wxMenuBar* m_editor_menubar{ nullptr }; + wxMenuBar* m_gcodeviewer_menubar{ nullptr }; + + struct RestoreFromGCodeViewer + { + bool collapsed_sidebar{ false }; + bool collapse_toolbar_enabled{ false }; + }; + + RestoreFromGCodeViewer m_restore_from_gcode_viewer; +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #if 0 wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now #endif @@ -120,6 +135,20 @@ class MainFrame : public DPIFrame slDlg, } m_layout; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +public: + enum class EMode : unsigned char + { + Editor, + GCodeViewer + }; + +private: + EMode m_mode{ EMode::Editor }; +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + protected: virtual void on_dpi_changed(const wxRect &suggested_rect); virtual void on_sys_color_changed() override; @@ -138,8 +167,21 @@ public: void init_tabpanel(); void create_preset_tabs(); void add_created_tab(Tab* panel); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + void init_editor_menubar(); + void update_editor_menubar(); + void init_gcodeviewer_menubar(); + + EMode get_mode() const { return m_mode; } + void set_mode(EMode mode); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void init_menubar(); void update_menubar(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void update_ui_from_settings(); bool is_loaded() const { return m_loaded; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fbe78218e..b8c4042a3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -33,9 +33,17 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +#include "libslic3r/GCode/GCodeProcessor.hpp" +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/PreviewData.hpp" #endif // !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/SLA/Hollowing.hpp" @@ -1595,6 +1603,7 @@ struct Plater::priv Mouse3DController mouse3d_controller; View3D* view3D; GLToolbar view_toolbar; + GLToolbar collapse_toolbar; Preview *preview; BackgroundSlicingProcess background_process; @@ -1689,6 +1698,8 @@ struct Plater::priv void reset_canvas_volumes(); bool init_view_toolbar(); + bool init_collapse_toolbar(); + #if ENABLE_GCODE_VIEWER void update_preview_bottom_toolbar(); void update_preview_moves_slider(); @@ -1888,6 +1899,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") + , collapse_toolbar(GLToolbar::Normal, "Collapse") , m_project_filename(wxEmptyString) { this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -2722,6 +2734,12 @@ void Plater::priv::reset() this->sidebar->show_sliced_info_sizer(false); model.custom_gcode_per_print_z.gcodes.clear(); + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + gcode_result.reset(); +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void Plater::priv::mirror(Axis axis) @@ -3949,6 +3967,53 @@ bool Plater::priv::init_view_toolbar() return true; } +bool Plater::priv::init_collapse_toolbar() +{ + if (collapse_toolbar.get_items_count() > 0) + // already initialized + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!collapse_toolbar.init(background_data)) + return false; + + collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); + collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + collapse_toolbar.set_border(5.0f); + collapse_toolbar.set_separator_size(5); + collapse_toolbar.set_gap_size(2); + + GLToolbarItem::Data item; + + item.name = "collapse_sidebar"; + item.icon_filename = "collapse.svg"; + item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel")); + item.sprite_id = 0; + item.left.action_callback = [this, item]() { + std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? + _utf8(L("Collapse right panel")) : _utf8(L("Expand right panel")); + + int id = collapse_toolbar.get_item_id("collapse_sidebar"); + collapse_toolbar.set_tooltip(id, new_tooltip); + + wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); + }; + + if (!collapse_toolbar.add_item(item)) + return false; + + collapse_toolbar.set_enabled(true); + + return true; +} + #if ENABLE_GCODE_VIEWER void Plater::priv::update_preview_bottom_toolbar() { @@ -4487,6 +4552,39 @@ void Plater::extract_config_from_project() load_files(input_paths, false, true); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +void Plater::load_gcode() +{ + // Ask user for a gcode file name. + wxString input_file; + wxGetApp().load_gcode(this, input_file); + // And finally load the gcode file. + load_gcode(input_file); +} + +void Plater::load_gcode(const wxString& filename) +{ + if (filename.empty()) + return; + +// p->gcode_result.reset(); + + wxBusyCursor wait; + wxBusyInfo info(_L("Processing GCode") + "...", get_current_canvas3D()->get_wxglcanvas()); + + GCodeProcessor processor; +// processor.apply_config(config); + processor.process_file(filename.ToUTF8().data()); + p->gcode_result = std::move(processor.extract_result()); + +// select_view_3D("Preview"); + p->preview->load_print(false); +// p->preview->load_gcode_preview(); +} +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); } // To be called when providing a list of files to the GUI slic3r on command line. @@ -5418,7 +5516,15 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); if (wxGetApp().mainframe) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + wxGetApp().mainframe->update_editor_menubar(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxGetApp().mainframe->update_menubar(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ p->update_main_toolbar_tooltips(); @@ -5570,6 +5676,29 @@ bool Plater::init_view_toolbar() return p->init_view_toolbar(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +void Plater::enable_view_toolbar(bool enable) +{ + p->view_toolbar.set_enabled(enable); +} +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + +bool Plater::init_collapse_toolbar() +{ + return p->init_collapse_toolbar(); +} + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +void Plater::enable_collapse_toolbar(bool enable) +{ + p->collapse_toolbar.set_enabled(enable); +} +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + const Camera& Plater::get_camera() const { return p->camera; @@ -5613,6 +5742,16 @@ GLToolbar& Plater::get_view_toolbar() return p->view_toolbar; } +const GLToolbar& Plater::get_collapse_toolbar() const +{ + return p->collapse_toolbar; +} + +GLToolbar& Plater::get_collapse_toolbar() +{ + return p->collapse_toolbar; +} + #if ENABLE_GCODE_VIEWER void Plater::update_preview_bottom_toolbar() { @@ -5682,6 +5821,11 @@ bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE +void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 56420a624..92b46c937 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -173,6 +173,12 @@ public: void add_model(bool imperial_units = false); void import_sl1_archive(); void extract_config_from_project(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + void load_gcode(); + void load_gcode(const wxString& filename); +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false); // To be called when providing a list of files to the GUI slic3r on command line. @@ -252,6 +258,11 @@ public: bool search_string_getter(int idx, const char** label, const char** tooltip); // For the memory statistics. const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + void clear_undo_redo_stack_main(); +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. void enter_gizmos_stack(); void leave_gizmos_stack(); @@ -315,6 +326,17 @@ public: void sys_color_changed(); bool init_view_toolbar(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + void enable_view_toolbar(bool enable); +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + bool init_collapse_toolbar(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_AS_STATE + void enable_collapse_toolbar(bool enable); +#endif // ENABLE_GCODE_VIEWER_AS_STATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const Camera& get_camera() const; Camera& get_camera(); @@ -330,6 +352,9 @@ public: const GLToolbar& get_view_toolbar() const; GLToolbar& get_view_toolbar(); + const GLToolbar& get_collapse_toolbar() const; + GLToolbar& get_collapse_toolbar(); + #if ENABLE_GCODE_VIEWER void update_preview_bottom_toolbar(); void update_preview_moves_slider(); diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index 4615a93ae..ef05119ad 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #define BOOST_VORONOI_USE_GMP 1 #include "boost/polygon/voronoi.hpp" @@ -16,12 +17,7 @@ using boost::polygon::voronoi_diagram; using namespace Slic3r; -struct VD : public boost::polygon::voronoi_diagram { - typedef double coord_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; -}; +using VD = Geometry::VoronoiDiagram; // #define VORONOI_DEBUG_OUT @@ -322,6 +318,7 @@ static inline void dump_voronoi_to_svg( /* const */ VD &vd, const Points &points, const Lines &lines, + const Polygons &offset_curves = Polygons(), const double scale = 0.7) // 0.2? { const std::string inputSegmentPointColor = "lightseagreen"; @@ -336,6 +333,9 @@ static inline void dump_voronoi_to_svg( const std::string voronoiArcColor = "red"; const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR); + const std::string offsetCurveColor = "magenta"; + const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR); + const bool internalEdgesOnly = false; const bool primaryEdgesOnly = false; @@ -408,6 +408,7 @@ static inline void dump_voronoi_to_svg( } #endif + svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth); svg.Close(); } #endif @@ -1585,6 +1586,32 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(), - vd, Points(), lines, 0.015); + vd, Points(), lines, Polygons(), 0.015); #endif } + +TEST_CASE("Voronoi offset", "[VoronoiOffset]") +{ + Polygons poly_with_hole = { Polygon { + { 0, 10000000}, + { 700000, 0}, + { 700000, 9000000}, + { 9100000, 9000000}, + { 9100000, 0}, + {10000000, 10000000} + } + }; + + VD vd; + Lines lines = to_lines(poly_with_hole); + construct_voronoi(lines.begin(), lines.end(), &vd); + + Polygons offsetted_polygons = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005)); + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset.svg").c_str(), + vd, Points(), lines, offsetted_polygons); +#endif + + REQUIRE(offsetted_polygons.size() == 2); +}