diff --git a/CMakeLists.txt b/CMakeLists.txt index a3b944532..dfc180c76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,7 +312,7 @@ endif() # boost::process was introduced first in version 1.64.0, # boost::beast::detail::base64 was introduced first in version 1.66.0 set(MINIMUM_BOOST_VERSION "1.66.0") -set(_boost_components "system;filesystem;thread;log;locale;regex;chrono;atomic;date_time") +set(_boost_components "system;filesystem;thread;log;locale;regex;chrono;atomic;date_time;iostreams") find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS ${_boost_components}) add_library(boost_libs INTERFACE) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index aef6222a3..d7e72b4ad 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -757,8 +757,16 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); m_processor.process_file(path_tmp, true, [print]() { print->throw_if_canceled(); }); DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); +#if ENABLE_GCODE_WINDOW + if (result != nullptr) { + *result = std::move(m_processor.extract_result()); + // set the filename to the correct value + result->filename = path; + } +#else if (result != nullptr) *result = std::move(m_processor.extract_result()); +#endif // ENABLE_GCODE_WINDOW BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info(); if (rename_file(path_tmp, path)) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 73ad25496..bd4e7a3ad 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -9,6 +9,9 @@ #endif // ENABLE_VALIDATE_CUSTOM_GCODE #include #include +#if ENABLE_GCODE_WINDOW +#include +#endif // ENABLE_GCODE_WINDOW #include #include @@ -974,6 +977,9 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr } // process gcode +#if ENABLE_GCODE_WINDOW + m_result.filename = filename; +#endif // ENABLE_GCODE_WINDOW m_result.id = ++s_result_id; // 1st move must be a dummy move m_result.moves.emplace_back(MoveVertex()); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index c1497edda..f6625452f 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -337,13 +337,15 @@ namespace Slic3r { std::vector filament; std::string printer; - void reset() - { + void reset() { print = ""; filament = std::vector(); printer = ""; } }; +#if ENABLE_GCODE_WINDOW + std::string filename; +#endif // ENABLE_GCODE_WINDOW unsigned int id; std::vector moves; Pointfs bed_shape; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 55de9637a..63ee79805 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -53,6 +53,8 @@ #define ENABLE_GCODE_LINES_ID_IN_H_SLIDER (1 && ENABLE_2_3_1_ALPHA1) // Enable validation of custom gcode against gcode processor reserved keywords #define ENABLE_VALIDATE_CUSTOM_GCODE (1 && ENABLE_2_3_1_ALPHA1) +// Enable showing a imgui window containing gcode in preview +#define ENABLE_GCODE_WINDOW (1 && ENABLE_2_3_1_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 66762eaee..17e3ac68c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -21,6 +21,9 @@ #include #include +#if ENABLE_GCODE_WINDOW +#include +#endif // ENABLE_GCODE_WINDOW #include #include #include @@ -280,7 +283,7 @@ void GCodeViewer::SequentialView::Marker::render() const imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":"); ImGui::SameLine(); char buf[1024]; - sprintf(buf, "X: %.2f, Y: %.2f, Z: %.2f", m_world_position(0), m_world_position(1), m_world_position(2)); + sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", m_world_position(0), m_world_position(1), m_world_position(2)); imgui.text(std::string(buf)); // force extra frame to automatically update window size @@ -297,6 +300,223 @@ void GCodeViewer::SequentialView::Marker::render() const ImGui::PopStyleVar(); } +#if ENABLE_GCODE_WINDOW +void GCodeViewer::SequentialView::GCodeWindow::load_gcode() +{ + if (m_filename.empty()) + return; + + try + { + // generate mapping for accessing data in file by line number + boost::nowide::ifstream f(m_filename); + + f.seekg(0, f.end); + uint64_t file_length = static_cast(f.tellg()); + f.seekg(0, f.beg); + + std::string line; + uint64_t offset = 0; + while (std::getline(f, line)) { + uint64_t line_length = static_cast(line.length()); + m_lines_map.push_back({ offset, line_length }); + offset += static_cast(line_length) + 1; + } + + if (offset != file_length) { + // if the final offset does not match with file length, lines are terminated with CR+LF + // so update all offsets accordingly + for (size_t i = 0; i < m_lines_map.size(); ++i) { + m_lines_map[i].first += static_cast(i); + } + } + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "Unable to load data from " << m_filename << ". Cannot show G-code window."; + reset(); + return; + } + + m_selected_line_id = 0; + m_last_lines_size = 0; + + try + { + m_file.open(boost::filesystem::path(m_filename)); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window."; + reset(); + } +} + +void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const +{ + auto update_lines = [this](uint64_t start_id, uint64_t end_id) { + std::vector ret; + ret.reserve(end_id - start_id + 1); + for (uint64_t id = start_id; id <= end_id; ++id) { + // read line from file + std::string gline(m_file.data() + m_lines_map[id - 1].first, m_lines_map[id - 1].second); + + std::string command; + std::string parameters; + std::string comment; + + // extract comment + std::vector tokens; + boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) + comment = ";" + tokens.back(); + + // extract gcode command and parameters + if (!command.empty()) { + boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) { + for (size_t i = 1; i < tokens.size(); ++i) { + parameters += " " + tokens[i]; + } + } + } + ret.push_back({ command, parameters, comment }); + } + return ret; + }; + + static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; + static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK; + static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; + static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; + static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; + + if (!m_visible || m_filename.empty() || m_lines_map.empty() || curr_line_id == 0) + return; + + // window height + const float wnd_height = bottom - top; + + // number of visible lines + const float text_height = ImGui::CalcTextSize("0").y; + const ImGuiStyle& style = ImGui::GetStyle(); + const uint64_t lines_count = static_cast((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y)); + + if (lines_count == 0) + return; + + // visible range + const uint64_t half_lines_count = lines_count / 2; + uint64_t start_id = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 0; + uint64_t end_id = start_id + lines_count - 1; + if (end_id >= static_cast(m_lines_map.size())) { + end_id = static_cast(m_lines_map.size()) - 1; + start_id = end_id - lines_count + 1; + } + + // updates list of lines to show, if needed + if (m_selected_line_id != curr_line_id || m_last_lines_size != end_id - start_id + 1) { + try + { + *const_cast*>(&m_lines) = update_lines(start_id, end_id); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "Error while loading from file " << m_filename << ". Cannot show G-code window."; + return; + } + *const_cast(&m_selected_line_id) = curr_line_id; + *const_cast(&m_last_lines_size) = m_lines.size(); + } + + // line number's column width + const float id_width = ImGui::CalcTextSize(std::to_string(end_id).c_str()).x; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); + imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); + imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + + // center the text in the window by pushing down the first line + const float f_lines_count = static_cast(lines_count); + ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y)); + + // render text lines + for (uint64_t id = start_id; id <= end_id; ++id) { + const Line& line = m_lines[id - start_id]; + + // rect around the current selected line + if (id == curr_line_id) { + const float pos_y = ImGui::GetCursorScreenPos().y; + const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y; + const float half_padding_x = 0.5f * style.WindowPadding.x; + ImGui::GetWindowDrawList()->AddRect({ half_padding_x, pos_y - half_ItemSpacing_y }, + { ImGui::GetCurrentWindow()->Size.x - half_padding_x, pos_y + text_height + half_ItemSpacing_y }, + ImGui::GetColorU32(SELECTION_RECT_COLOR)); + } + + // render line number + const std::string id_str = std::to_string(id); + // spacer to right align text + ImGui::Dummy({ id_width - ImGui::CalcTextSize(id_str.c_str()).x, text_height }); + ImGui::SameLine(0.0f, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, LINE_NUMBER_COLOR); + imgui.text(id_str); + ImGui::PopStyleColor(); + + if (!line.command.empty() || !line.comment.empty()) + ImGui::SameLine(); + + // render command + if (!line.command.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, COMMAND_COLOR); + imgui.text(line.command); + ImGui::PopStyleColor(); + } + + // render parameters + if (!line.parameters.empty()) { + ImGui::SameLine(0.0f, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, PARAMETERS_COLOR); + imgui.text(line.parameters); + ImGui::PopStyleColor(); + } + + // render comment + if (!line.comment.empty()) { + if (!line.command.empty()) + ImGui::SameLine(0.0f, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, COMMENT_COLOR); + imgui.text(line.comment); + ImGui::PopStyleColor(); + } + } + + imgui.end(); + ImGui::PopStyleVar(); +} + +void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() +{ + if (m_file.is_open()) + m_file.close(); +} + +void GCodeViewer::SequentialView::render(float legend_height) const +{ + marker.render(); + float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); + if (wxGetApp().is_editor()) + bottom -= wxGetApp().plater()->get_view_toolbar().get_height(); + gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); +} +#endif // ENABLE_GCODE_WINDOW + const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.75f, 0.75f, 0.75f }, // erNone { 1.00f, 0.90f, 0.30f }, // erPerimeter @@ -395,6 +615,11 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& // release gpu memory, if used reset(); +#if ENABLE_GCODE_WINDOW + m_sequential_view.gcode_window.set_filename(gcode_result.filename); + m_sequential_view.gcode_window.load_gcode(); +#endif // ENABLE_GCODE_WINDOW + load_toolpaths(gcode_result); if (m_layers.empty()) @@ -548,7 +773,9 @@ void GCodeViewer::reset() m_layers_z_range = { 0, 0 }; m_roles = std::vector(); m_time_statistics.reset(); - +#if ENABLE_GCODE_WINDOW + m_sequential_view.gcode_window.reset(); +#endif // ENABLE_GCODE_WINDOW #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -608,13 +835,22 @@ void GCodeViewer::render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); + render_shells(); +#if ENABLE_GCODE_WINDOW + float legend_height = 0.0f; + render_legend(legend_height); +#else + render_legend(); +#endif // ENABLE_GCODE_WINDOW SequentialView* sequential_view = const_cast(&m_sequential_view); if (sequential_view->current.last != sequential_view->endpoints.last) { sequential_view->marker.set_world_position(sequential_view->current_position); +#if ENABLE_GCODE_WINDOW + sequential_view->render(legend_height); +#else sequential_view->marker.render(); +#endif // ENABLE_GCODE_WINDOW } - render_shells(); - render_legend(); #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -3767,7 +4003,11 @@ void GCodeViewer::render_shells() const // glsafe(::glDepthMask(GL_TRUE)); } +#if ENABLE_GCODE_WINDOW +void GCodeViewer::render_legend(float& legend_height) const +#else void GCodeViewer::render_legend() const +#endif // ENABLE_GCODE_WINDOW { if (!m_legend_enabled) return; @@ -4452,6 +4692,10 @@ void GCodeViewer::render_legend() const } } +#if ENABLE_GCODE_WINDOW + legend_height = ImGui::GetCurrentWindow()->Size.y; +#endif // ENABLE_GCODE_WINDOW + imgui.end(); ImGui::PopStyleVar(); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 2abbb81f1..cc897c7bc 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -5,6 +5,10 @@ #include "libslic3r/GCode/GCodeProcessor.hpp" #include "GLModel.hpp" +#if ENABLE_GCODE_WINDOW +#include +#endif // ENABLE_GCODE_WINDOW + #include #include #include @@ -606,6 +610,46 @@ public: void render() const; }; +#if ENABLE_GCODE_WINDOW + class GCodeWindow + { + struct Line + { + std::string command; + std::string parameters; + std::string comment; + }; + bool m_visible{ true }; + uint64_t m_selected_line_id{ 0 }; + size_t m_last_lines_size{ 0 }; + std::string m_filename; + boost::iostreams::mapped_file_source m_file; + // map for accessing data in file by line number + std::vector> m_lines_map; + // current visible lines + std::vector m_lines; + + public: + GCodeWindow() = default; + ~GCodeWindow() { stop_mapping_file(); } + void set_filename(const std::string& filename) { m_filename = filename; } + void load_gcode(); + void reset() { + stop_mapping_file(); + m_lines_map.clear(); + m_lines.clear(); + m_filename.clear(); + } + + void toggle_visibility() { m_visible = !m_visible; } + + void render(float top, float bottom, uint64_t curr_line_id) const; + + private: + void stop_mapping_file(); + }; +#endif // ENABLE_GCODE_WINDOW + struct Endpoints { size_t first{ 0 }; @@ -618,9 +662,16 @@ public: Endpoints last_current; Vec3f current_position{ Vec3f::Zero() }; Marker marker; +#if ENABLE_GCODE_WINDOW + GCodeWindow gcode_window; +#endif // ENABLE_GCODE_WINDOW #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER std::vector gcode_ids; #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + +#if ENABLE_GCODE_WINDOW + void render(float legend_height) const; +#endif // ENABLE_GCODE_WINDOW }; enum class EViewType : unsigned char @@ -715,13 +766,21 @@ public: void export_toolpaths_to_obj(const char* filename) const; +#if ENABLE_GCODE_WINDOW + void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); } +#endif // ENABLE_GCODE_WINDOW + private: void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; void render_toolpaths() const; void render_shells() const; +#if ENABLE_GCODE_WINDOW + void render_legend(float& legend_height) const; +#else void render_legend() const; +#endif // ENABLE_GCODE_WINDOW #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5352e21b3..7bbdc72b1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2602,6 +2602,10 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } case 'B': case 'b': { zoom_to_bed(); break; } +#if ENABLE_GCODE_WINDOW + case 'C': + case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } +#endif // ENABLE_GCODE_WINDOW case 'E': case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } case 'G': diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index a7363837e..1c451672c 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -196,6 +196,9 @@ void KBShortcutsDialog::fill_shortcuts() { "D", L("Horizontal slider - Move active thumb Right") }, { "X", L("On/Off one layer mode of the vertical slider") }, { "L", L("Show/Hide Legend and Estimated printing time") }, +#if ENABLE_GCODE_WINDOW + { "C", L("Show/Hide G-code window") }, +#endif // ENABLE_GCODE_WINDOW }; m_full_shortcuts.push_back({ { _L("Preview"), "" }, preview_shortcuts });