mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Added new tech ENABLE_GCODE_VIEWER_AS_STATE -> GCodeViewer as a new application state (WIP) + fix of conflicts after merge with master
This commit is contained in:
		
						commit
						70a6fb0e20
					
				
					 19 changed files with 1209 additions and 252 deletions
				
			
		| 
						 | 
				
			
			@ -190,6 +190,8 @@ add_library(libslic3r STATIC
 | 
			
		|||
    Time.cpp
 | 
			
		||||
    Time.hpp
 | 
			
		||||
    MTUtils.hpp
 | 
			
		||||
    VoronoiOffset.cpp
 | 
			
		||||
    VoronoiOffset.hpp
 | 
			
		||||
    Zipper.hpp
 | 
			
		||||
    Zipper.cpp
 | 
			
		||||
    MinAreaBoundingBox.hpp
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<float>();
 | 
			
		||||
        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<unsigned int>(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<unsigned char>(std::stoi(command.substr(1)));
 | 
			
		||||
            if (m_extruder_id != id)
 | 
			
		||||
            {
 | 
			
		||||
            if (m_extruder_id != id) {
 | 
			
		||||
                unsigned char extruders_count = static_cast<unsigned char>(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];
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -252,8 +252,16 @@ bool arrange(
 | 
			
		|||
    // output
 | 
			
		||||
    Pointfs &positions);
 | 
			
		||||
 | 
			
		||||
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
 | 
			
		||||
public:
 | 
			
		||||
    typedef double                                          coord_type;
 | 
			
		||||
    typedef boost::polygon::point_data<coordinate_type>     point_type;
 | 
			
		||||
    typedef boost::polygon::segment_data<coordinate_type>   segment_type;
 | 
			
		||||
    typedef boost::polygon::rectangle_data<coordinate_type> 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<double> {
 | 
			
		||||
    public:
 | 
			
		||||
        typedef double                                          coord_type;
 | 
			
		||||
        typedef boost::polygon::point_data<coordinate_type>     point_type;
 | 
			
		||||
        typedef boost::polygon::segment_data<coordinate_type>   segment_type;
 | 
			
		||||
        typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
 | 
			
		||||
    };
 | 
			
		||||
private:
 | 
			
		||||
    using VD = VoronoiDiagram;
 | 
			
		||||
    VD vd;
 | 
			
		||||
    std::set<const VD::edge_type*> edges, valid_edges;
 | 
			
		||||
    std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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_
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										393
									
								
								src/libslic3r/VoronoiOffset.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								src/libslic3r/VoronoiOffset.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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 <cmath>
 | 
			
		||||
 | 
			
		||||
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<int, std::array<double, 2>> 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<double> 	&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<double> 	&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<double> vertex_dist(vd.num_vertices(), std::numeric_limits<double>::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<double> edge_dist(vd.num_edges(), std::numeric_limits<double>::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<double>::max();
 | 
			
		||||
		    	if (v0 == nullptr && v1 == nullptr) {
 | 
			
		||||
                    dmin = (pt1.cast<double>() - pt0.cast<double>()).norm();
 | 
			
		||||
		    	} else {
 | 
			
		||||
                    Vec2d pt((pt0 + pt1).cast<double>() * 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<double>().norm() < SCALED_EPSILON);
 | 
			
		||||
                    d0 = dmin = 0.;
 | 
			
		||||
                    vertex_dist[v0 - &vd.vertices().front()] = d0;
 | 
			
		||||
                } else {
 | 
			
		||||
                    assert((Point(v1->x(), v1->y()) - pt).cast<double>().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<double>());
 | 
			
		||||
                Vec2d  dir(line0.b.cast<double>() - 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 <v0, v1>.
 | 
			
		||||
	            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<unsigned char> 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<double>::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<double>::max();
 | 
			
		||||
        double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits<double>::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<double>::max();
 | 
			
		||||
            double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits<double>::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<double>() - point).norm() :
 | 
			
		||||
            line.distance_to(point.cast<coord_t>());
 | 
			
		||||
	};
 | 
			
		||||
#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<double>();
 | 
			
		||||
					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
 | 
			
		||||
							
								
								
									
										14
									
								
								src/libslic3r/VoronoiOffset.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/libslic3r/VoronoiOffset.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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_
 | 
			
		||||
| 
						 | 
				
			
			@ -45,12 +45,19 @@
 | 
			
		|||
#include <Eigen/Dense>
 | 
			
		||||
 | 
			
		||||
#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 {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,20 +10,20 @@
 | 
			
		|||
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
#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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1238,7 +1238,7 @@ bool GLToolbar::generate_icons_texture() const
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<std::pair<int, bool>> 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<void()> cb, const std::string& err);
 | 
			
		||||
 | 
			
		||||
    void            persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 <wx/notebook.h>
 | 
			
		||||
#include <wx/glcanvas.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<bool(void)> 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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<size_t> Plater::load_files(const std::vector<fs::path>& 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(); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<size_t> load_files(const std::vector<boost::filesystem::path>& 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();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@
 | 
			
		|||
#include <libslic3r/Polyline.hpp>
 | 
			
		||||
#include <libslic3r/EdgeGrid.hpp>
 | 
			
		||||
#include <libslic3r/Geometry.hpp>
 | 
			
		||||
#include <libslic3r/VoronoiOffset.hpp>
 | 
			
		||||
 | 
			
		||||
#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<double> {
 | 
			
		||||
    typedef double                                          coord_type;
 | 
			
		||||
    typedef boost::polygon::point_data<coordinate_type>     point_type;
 | 
			
		||||
    typedef boost::polygon::segment_data<coordinate_type>   segment_type;
 | 
			
		||||
    typedef boost::polygon::rectangle_data<coordinate_type> 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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue