#include "libslic3r/libslic3r.h" #include "GCodeViewer.hpp" #include "libslic3r/BuildVolume.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/LocalesUtils.hpp" #include "libslic3r/PresetBundle.hpp" //BBS: add convex hull logic for toolpath check #include "libslic3r/Geometry/ConvexHull.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" #include "Plater.hpp" #include "Camera.hpp" #include "I18N.hpp" #include "GUI_Utils.hpp" #include "GUI.hpp" #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/Layer.hpp" #include "Widgets/ProgressDialog.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace Slic3r { namespace GUI { //BBS translation of EViewType //const std::string EViewType_Map[(int) GCodeViewer::EViewType::Count] = { // _u8L("Line Type"), // _u8L("Layer Height"), // _u8L("Line Width"), // _u8L("Speed"), // _u8L("Fan Speed"), // _u8L("Temperature"), // _u8L("Flow"), // _u8L("Tool"), // _u8L("Filament") // }; static std::string get_view_type_string(GCodeViewer::EViewType view_type) { if (view_type == GCodeViewer::EViewType::FeatureType) return _u8L("Line Type"); else if (view_type == GCodeViewer::EViewType::Height) return _u8L("Layer Height"); else if (view_type == GCodeViewer::EViewType::Width) return _u8L("Line Width"); else if (view_type == GCodeViewer::EViewType::Feedrate) return _u8L("Speed"); else if (view_type == GCodeViewer::EViewType::FanSpeed) return _u8L("Fan Speed"); else if (view_type == GCodeViewer::EViewType::Temperature) return _u8L("Temperature"); else if (view_type == GCodeViewer::EViewType::VolumetricRate) return _u8L("Flow"); else if (view_type == GCodeViewer::EViewType::Tool) return _u8L("Tool"); else if (view_type == GCodeViewer::EViewType::ColorPrint) return _u8L("Filament"); else if (view_type == GCodeViewer::EViewType::LayerTime) return _u8L("Layer Time"); return ""; } static unsigned char buffer_id(EMoveType type) { return static_cast(type) - static_cast(EMoveType::Retract); } static EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(EMoveType::Retract) + id); } static std::array decode_color(const std::string& color) { static const float INV_255 = 1.0f / 255.0f; std::array ret = { 0.0f, 0.0f, 0.0f, 1.0f }; const char* c = color.data() + 1; if (color.size() == 7 && color.front() == '#') { for (size_t j = 0; j < 3; ++j) { int digit1 = hex_digit_to_int(*c++); int digit2 = hex_digit_to_int(*c++); if (digit1 == -1 || digit2 == -1) break; ret[j] = float(digit1 * 16 + digit2) * INV_255; } } else if (color.size() == 9 && color.front() == '#') { for (size_t j = 0; j < 4; ++j) { int digit1 = hex_digit_to_int(*c++); int digit2 = hex_digit_to_int(*c++); if (digit1 == -1 || digit2 == -1) break; ret[j] = float(digit1 * 16 + digit2) * INV_255; } } return ret; } static std::vector> decode_colors(const std::vector& colors) { std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f, 1.0f }); for (size_t i = 0; i < colors.size(); ++i) { output[i] = decode_color(colors[i]); } return output; } // Round to a bin with minimum two digits resolution. // Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. static float round_to_bin(const float value) { // assert(value > 0); constexpr float const scale [5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f }; constexpr float const invscale [5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f }; constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f }; // Scaling factor, pointer to the tables above. int i = 0; // While the scaling factor is not yet large enough to get two integer digits after scaling and rounding: for (; value < threshold[i] && i < 4; ++ i) ; return std::round(value * scale[i]) * invscale[i]; } // Find an index of a value in a sorted vector, which is in . // Returns -1 if there is no such member. static int find_close_layer_idx(const std::vector &zs, double &z, double eps) { if (zs.empty()) return -1; auto it_h = std::lower_bound(zs.begin(), zs.end(), z); if (it_h == zs.end()) { auto it_l = it_h; --it_l; if (z - *it_l < eps) return int(zs.size() - 1); } else if (it_h == zs.begin()) { if (*it_h - z < eps) return 0; } else { auto it_l = it_h; --it_l; double dist_l = z - *it_l; double dist_h = *it_h - z; if (std::min(dist_l, dist_h) < eps) { return (dist_l < dist_h) ? int(it_l - zs.begin()) : int(it_h - zs.begin()); } } return -1; } void GCodeViewer::VBuffer::reset() { // release gpu memory if (!vbos.empty()) { glsafe(::glDeleteBuffers(static_cast(vbos.size()), static_cast(vbos.data()))); vbos.clear(); } sizes.clear(); count = 0; } void GCodeViewer::InstanceVBuffer::Ranges::reset() { for (Range& range : ranges) { // release gpu memory if (range.vbo > 0) glsafe(::glDeleteBuffers(1, &range.vbo)); } ranges.clear(); } void GCodeViewer::InstanceVBuffer::reset() { s_ids.clear(); s_ids.shrink_to_fit(); buffer.clear(); buffer.shrink_to_fit(); render_ranges.reset(); } void GCodeViewer::IBuffer::reset() { // release gpu memory if (ibo > 0) { glsafe(::glDeleteBuffers(1, &ibo)); ibo = 0; } vbo = 0; count = 0; } bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const { auto matches_percent = [](float value1, float value2, float max_percent) { return std::abs(value2 - value1) / value1 <= max_percent; }; switch (move.type) { case EMoveType::Tool_change: case EMoveType::Color_change: case EMoveType::Pause_Print: case EMoveType::Custom_GCode: case EMoveType::Retract: case EMoveType::Unretract: case EMoveType::Seam: case EMoveType::Extrude: { // use rounding to reduce the number of generated paths return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && height == round_to_bin(move.height) && width == round_to_bin(move.width) && matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f) && layer_time == move.layer_duration; } case EMoveType::Travel: { return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; } default: { return false; } } } void GCodeViewer::TBuffer::Model::reset() { instances.reset(); } void GCodeViewer::TBuffer::reset() { vertices.reset(); for (IBuffer& buffer : indices) { buffer.reset(); } indices.clear(); paths.clear(); render_paths.clear(); model.reset(); } void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, round_to_bin(move.height), round_to_bin(move.width), move.feedrate, move.fan_speed, move.temperature, move.volumetric_rate(), move.layer_duration, move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); } GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const { float global_t = 0.0f; const float step = step_size(type); if (step > 0.0f) { switch (type) { default: case EType::Linear: { global_t = (value > min) ? (value - min) / step : 0.0f; break; } case EType::Logarithmic: { global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f; break; } } } const size_t color_max_idx = Range_Colors.size() - 1; // Compute the two colors just below (low) and above (high) the input value const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); // Compute how far the value is between the low and high colors so that they can be interpolated const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); // Interpolate between the low and high colors to find exactly which color the input value should get auto color = lerp(ColorRGBA(Range_Colors[color_low_idx]), ColorRGBA(Range_Colors[color_high_idx]), local_t); return color.get_data(); } float GCodeViewer::Extrusions::Range::step_size(EType type) const { switch (type) { default: case EType::Linear: { return (max > min) ? (max - min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } case EType::Logarithmic: { return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } } } float GCodeViewer::Extrusions::Range::get_value_at_step(int step) const { if (!log_scale) return min + static_cast(step) * step_size(); else // calculate log-average { float min_range = min; if (min_range == 0) min_range = 0.0001f; float step_size = std::log(max / min_range) / (static_cast(Range_Colors.size()) - 1.0f); return std::exp(std::log(min) + static_cast(step) * step_size); } } GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { if (ibo > 0) glsafe(::glDeleteBuffers(1, &ibo)); } void GCodeViewer::SequentialRangeCap::reset() { if (ibo > 0) glsafe(::glDeleteBuffers(1, &ibo)); buffer = nullptr; ibo = 0; vbo = 0; color = { 0.0f, 0.0f, 0.0f, 1.0f }; } void GCodeViewer::SequentialView::Marker::init(std::string filename) { if (filename.empty()) { m_model.init_from(stilized_arrow(16, 1.5f, 3.0f, 0.8f, 3.0f)); } else { m_model.init_from_file(filename); } m_model.set_color(-1, {1.0f, 1.0f, 1.0f, 0.5f}); } void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) { m_world_position = position; m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); } void GCodeViewer::SequentialView::Marker::update_curr_move(const GCodeProcessorResult::MoveVertex move) { m_curr_move = move; } //BBS: GUI refactor: add canvas size from parameters void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_height, const EViewType& view_type) const { if (!m_visible) return; GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); shader->start_using(); shader->set_uniform("emission_factor", 0.0f); glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(m_world_transform.data())); m_model.render(); glsafe(::glPopMatrix()); shader->stop_using(); glsafe(::glDisable(GL_BLEND)); static float last_window_width = 0.0f; size_t text_line = 0; static size_t last_text_line = 0; const ImU32 text_name_clr = m_is_dark ? IM_COL32(255, 255, 255, 0.88 * 255) : IM_COL32(38, 46, 48, 255); const ImU32 text_value_clr = m_is_dark ? IM_COL32(255, 255, 255, 0.4 * 255) : IM_COL32(144, 144, 144, 255); ImGuiWrapper& imgui = *wxGetApp().imgui(); //BBS: GUI refactor: add canvas size from parameters imgui.set_next_window_pos(0.5f * static_cast(canvas_width), static_cast(canvas_height), ImGuiCond_Always, 0.5f, 1.0f); imgui.push_toolbar_style(m_scale); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0, 4.0 * m_scale)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0 * m_scale, 6.0 * m_scale)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, text_name_clr); ImGui::PushStyleColor(ImGuiCol_Text, text_value_clr); imgui.begin(std::string("ExtruderPosition"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar); ImGui::AlignTextToFramePadding(); //BBS: minus the plate offset when show tool position PartPlateList& partplate_list = wxGetApp().plater()->get_partplate_list(); PartPlate* plate = partplate_list.get_curr_plate(); const Vec3f position = m_world_position + m_world_offset; std::string x = ImGui::ColorMarkerStart + std::string("X: ") + ImGui::ColorMarkerEnd; std::string y = ImGui::ColorMarkerStart + std::string("Y: ") + ImGui::ColorMarkerEnd; std::string z = ImGui::ColorMarkerStart + std::string("Z: ") + ImGui::ColorMarkerEnd; std::string height = ImGui::ColorMarkerStart + _u8L("Height: ") + ImGui::ColorMarkerEnd; std::string width = ImGui::ColorMarkerStart + _u8L("Width: ") + ImGui::ColorMarkerEnd; std::string speed = ImGui::ColorMarkerStart + _u8L("Speed: ") + ImGui::ColorMarkerEnd; std::string flow = ImGui::ColorMarkerStart + _u8L("Flow: ") + ImGui::ColorMarkerEnd; std::string layer_time = ImGui::ColorMarkerStart + _u8L("Layer Time: ") + ImGui::ColorMarkerEnd; std::string fanspeed = ImGui::ColorMarkerStart + _u8L("Fan Speed: ") + ImGui::ColorMarkerEnd; std::string temperature = ImGui::ColorMarkerStart + _u8L("Temperature: ") + ImGui::ColorMarkerEnd; const float item_size = imgui.calc_text_size("X: 000.000 ").x; const float item_spacing = imgui.get_item_spacing().x; const float window_padding = ImGui::GetStyle().WindowPadding.x; char buf[1024]; // extra text depends on whether current move is extrude type bool show_extra_text = m_curr_move.type == EMoveType::Extrude; // FeatureType and ColorPrint shall only show x,y,z if (view_type == EViewType::FeatureType || view_type == EViewType::ColorPrint) show_extra_text = false; // Feedrate and LayerTime shall always show extra text else if (view_type == EViewType::Feedrate || view_type == EViewType::LayerTime) show_extra_text = true; if (show_extra_text) { sprintf(buf, "%s%.3f", x.c_str(), position.x() - plate->get_origin().x()); ImGui::PushItemWidth(item_size); imgui.text(buf); ImGui::SameLine(window_padding + item_size + item_spacing); sprintf(buf, "%s%.3f", y.c_str(), position.y() - plate->get_origin().y()); ImGui::PushItemWidth(item_size); imgui.text(buf); sprintf(buf, "%s%.3f", z.c_str(), position.z()); ImGui::PushItemWidth(item_size); imgui.text(buf); switch (view_type) { case EViewType::Height: { ImGui::SameLine(window_padding + item_size + item_spacing); sprintf(buf, "%s%.2f", height.c_str(), m_curr_move.height); ImGui::PushItemWidth(item_size); imgui.text(buf); break; } case EViewType::Width: { ImGui::SameLine(window_padding + item_size + item_spacing); sprintf(buf, "%s%.2f", width.c_str(), m_curr_move.width); ImGui::PushItemWidth(item_size); imgui.text(buf); break; } case EViewType::Feedrate: { ImGui::SameLine(window_padding + item_size + item_spacing); sprintf(buf, "%s%.0f", speed.c_str(), m_curr_move.feedrate); ImGui::PushItemWidth(item_size); imgui.text(buf); break; } case EViewType::VolumetricRate: { ImGui::SameLine(window_padding + item_size + item_spacing); sprintf(buf, "%s%.2f", flow.c_str(), m_curr_move.volumetric_rate()); ImGui::PushItemWidth(item_size); imgui.text(buf); break; } case EViewType::FanSpeed: { ImGui::SameLine(window_padding + item_size + item_spacing); sprintf(buf, "%s%.0f", fanspeed.c_str(), m_curr_move.fan_speed); ImGui::PushItemWidth(item_size); imgui.text(buf); break; } case EViewType::Temperature: { ImGui::SameLine(window_padding + item_size + item_spacing); sprintf(buf, "%s%.0f", temperature.c_str(), m_curr_move.temperature); ImGui::PushItemWidth(item_size); imgui.text(buf); break; } case EViewType::LayerTime:{ ImGui::SameLine(window_padding + item_size + item_spacing); sprintf(buf, "%s%.1f", layer_time.c_str(), m_curr_move.layer_duration); ImGui::PushItemWidth(item_size); imgui.text(buf); break; } default: break; } text_line = 2; } else { sprintf(buf, "%s%.3f", x.c_str(), position.x() - plate->get_origin().x()); imgui.text(buf); ImGui::SameLine(); sprintf(buf, "%s%.3f", y.c_str(), position.y() - plate->get_origin().y()); imgui.text(buf); ImGui::SameLine(); sprintf(buf, "%s%.3f", z.c_str(), position.z()); imgui.text(buf); text_line = 1; } // force extra frame to automatically update window size float window_width = ImGui::GetWindowWidth(); if (window_width != last_window_width || text_line != last_text_line) { last_window_width = window_width; last_text_line = text_line; #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT imgui.set_requires_extra_frame(); #else wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT } imgui.end(); ImGui::PopStyleVar(2); ImGui::PopStyleColor(2); imgui.pop_toolbar_style(); } void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, const std::vector &lines_ends) { assert(! m_file.is_open()); if (m_file.is_open()) return; m_filename = filename; m_lines_ends = lines_ends; m_selected_line_id = 0; m_last_lines_size = 0; try { m_file.open(boost::filesystem::path(m_filename)); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": mapping file " << m_filename; } catch (...) { BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window."; reset(); } } //BBS: GUI refactor: move to right void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, float right, uint64_t curr_line_id) const //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 const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; const size_t len = m_lines_ends[id - 1] - start; std::string gline(m_file.data() + start, len); 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 = { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f }; static const ImVec4 LINE_NUMBER_COLOR = { 33.0f / 255.0f, 95.0f / 255.0f, 154.0f / 255.0f, 1.0f }; //static const ImVec4 SELECTION_RECT_COLOR = { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f }; static const ImVec4 SELECTION_RECT_COLOR = { 33.0f / 255.0f, 95.0f / 255.0f, 154.0f / 255.0f, 1.0f }; static const ImVec4 COMMAND_COLOR = m_is_dark ? ImVec4( 240.0f / 255.0f, 240.0f / 255.0f, 240.0f / 255.0f, 1.0f ) : ImVec4( 1.0f, 1.0f, 1.0f, 1.0f ); static const ImVec4 PARAMETERS_COLOR = m_is_dark ? ImVec4( 179.0f / 255.0f, 179.0f / 255.0f, 179.0f / 255.0f, 1.0f ) : ImVec4( 206.0f / 255.0f, 206.0f / 255.0f, 206.0f / 255.0f, 1.0f ); static const ImVec4 COMMENT_COLOR = m_is_dark ? ImVec4(129.0f / 255.0f, 129.0f / 255.0f, 129.0f / 255.0f, 1.0f) : ImVec4( 172.0f / 255.0f, 172.0f / 255.0f, 172.0f / 255.0f, 1.0f ); if (!m_visible || m_filename.empty() || m_lines_ends.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_ends.size())) { end_id = static_cast(m_lines_ends.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(); //BBS: GUI refactor: move to right //imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); imgui.set_next_window_pos(right, top, ImGuiCond_Always, 1.0f, 0.0f); imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.8f); 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) { //BBS: GUI refactor: move to right const float pos_y = ImGui::GetCursorScreenPos().y; const float pos_x = ImGui::GetCursorScreenPos().x; const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y; const float half_ItemSpacing_x = 0.5f * style.ItemSpacing.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)); ImGui::GetWindowDrawList()->AddRect({ pos_x - half_ItemSpacing_x, pos_y - half_ItemSpacing_y }, { right - half_ItemSpacing_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() { //BBS: add log to trace the gcode file issue if (m_file.is_open()) { m_file.close(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished mapping file " << m_filename; } } //BBS: GUI refactor: move to the right void GCodeViewer::SequentialView::render(float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) const { marker.render(canvas_width, canvas_height, view_type); //float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); // BBS #if 0 if (wxGetApp().is_editor()) bottom -= wxGetApp().plater()->get_view_toolbar().get_height(); #endif //gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); gcode_window.render(legend_height, (float)canvas_height, (float)canvas_width - (float)right_margin, static_cast(gcode_ids[current.last])); } const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.90f, 0.70f, 0.70f, 1.0f }, // erNone { 1.00f, 0.90f, 0.30f, 1.0f }, // erPerimeter { 1.00f, 0.49f, 0.22f, 1.0f }, // erExternalPerimeter { 0.12f, 0.12f, 1.00f, 1.0f }, // erOverhangPerimeter { 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill { 0.59f, 0.33f, 0.80f, 1.0f }, // erSolidInfill { 0.94f, 0.25f, 0.25f, 1.0f }, // erTopSolidInfill { 0.40f, 0.36f, 0.78f, 1.0f }, // erBottomSurface { 1.00f, 0.55f, 0.41f, 1.0f }, // erIroning { 0.30f, 0.50f, 0.73f, 1.0f }, // erBridgeInfill { 1.00f, 1.00f, 1.00f, 1.0f }, // erGapFill { 0.00f, 0.53f, 0.43f, 1.0f }, // erSkirt { 0.00f, 0.23f, 0.43f, 1.0f }, // erBrim { 0.00f, 1.00f, 0.00f, 1.0f }, // erSupportMaterial { 0.00f, 0.50f, 0.00f, 1.0f }, // erSupportMaterialInterface { 0.00f, 0.25f, 0.00f, 1.0f }, // erSupportTransition { 0.70f, 0.89f, 0.67f, 1.0f }, // erWipeTower { 0.37f, 0.82f, 0.58f, 1.0f } // erCustom }}; const std::vector GCodeViewer::Options_Colors {{ { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams { 0.758f, 0.744f, 0.389f, 1.0f }, // ToolChanges { 0.856f, 0.582f, 0.546f, 1.0f }, // ColorChanges { 0.322f, 0.942f, 0.512f, 1.0f }, // PausePrints { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes }}; const std::vector GCodeViewer::Travel_Colors {{ { 0.219f, 0.282f, 0.609f, 1.0f }, // Move { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude { 0.505f, 0.064f, 0.028f, 1.0f } // Retract }}; // Normal ranges // blue to red const std::vector GCodeViewer::Range_Colors{{ decode_color_to_float_array("#FF00FF"), // bluish decode_color_to_float_array("#FF55A9"), decode_color_to_float_array("#FE8778"), decode_color_to_float_array("#FFB847"), decode_color_to_float_array("#FFD925"), decode_color_to_float_array("#FFFF00"), decode_color_to_float_array("#D8FF00"), decode_color_to_float_array("#ADFF04"), decode_color_to_float_array("#76FF01"), decode_color_to_float_array("#00FF00") // reddish }}; //const std::vector GCodeViewer::Range_Colors {{ // {0.043f, 0.173f, 0.478f, 1.0f}, // bluish // {0.075f, 0.349f, 0.522f, 1.0f}, // {0.110f, 0.533f, 0.569f, 1.0f}, // {0.016f, 0.839f, 0.059f, 1.0f}, // {0.667f, 0.949f, 0.000f, 1.0f}, // {0.988f, 0.975f, 0.012f, 1.0f}, // {0.961f, 0.808f, 0.039f, 1.0f}, // //{0.890f, 0.533f, 0.125f, 1.0f}, // {0.820f, 0.408f, 0.188f, 1.0f}, // {0.761f, 0.322f, 0.235f, 1.0f}, // {0.581f, 0.149f, 0.087f, 1.0f} // reddish //}}; const GCodeViewer::Color GCodeViewer::Wipe_Color = { 1.0f, 1.0f, 0.0f, 1.0f }; const GCodeViewer::Color GCodeViewer::Neutral_Color = { 0.25f, 0.25f, 0.25f, 1.0f }; GCodeViewer::GCodeViewer() { m_moves_slider = new IMSlider(0, 0, 0, 100, wxSL_HORIZONTAL); m_layers_slider = new IMSlider(0, 0, 0, 100, wxSL_VERTICAL); m_extrusions.reset_role_visibility_flags(); // m_sequential_view.skip_invisible_moves = true; } GCodeViewer::~GCodeViewer() { reset(); if (m_moves_slider) { delete m_moves_slider; m_moves_slider = nullptr; } if (m_layers_slider) { delete m_layers_slider; m_layers_slider = nullptr; } } void GCodeViewer::init(ConfigOptionMode mode, PresetBundle* preset_bundle) { if (m_gl_data_initialized) return; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": enter, m_buffers.size=%1%") %m_buffers.size(); // initializes opengl data of TBuffers for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; EMoveType type = buffer_type(i); switch (type) { default: { break; } case EMoveType::Tool_change: case EMoveType::Color_change: case EMoveType::Pause_Print: case EMoveType::Custom_GCode: case EMoveType::Retract: case EMoveType::Unretract: case EMoveType::Seam: { // if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { // buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel; // buffer.shader = "gouraud_light_instanced"; // buffer.model.model.init_from(diamond(16)); // buffer.model.color = option_color(type); // buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel; // } // else { if(type == EMoveType::Seam) buffer.visible = true; buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; buffer.shader = "gouraud_light"; buffer.model.data = diamond(16); buffer.model.color = option_color(type); buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel; // } break; } case EMoveType::Wipe: case EMoveType::Extrude: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; buffer.shader = "gouraud_light"; break; } case EMoveType::Travel: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; buffer.shader = "toolpaths_lines"; break; } } set_toolpath_move_type_visible(EMoveType::Extrude, true); } // initializes tool marker std::string filename; if (preset_bundle != nullptr) { const Preset* curr = &preset_bundle->printers.get_selected_preset(); if (curr->is_system) filename = PresetUtils::system_printer_hotend_model(*curr); else { auto *printer_model = curr->config.opt("printer_model"); if (printer_model != nullptr && ! printer_model->value.empty()) { filename = preset_bundle->get_hotend_model_for_printer_model(printer_model->value); } if (filename.empty()) { filename = preset_bundle->get_hotend_model_for_printer_model(PresetBundle::BBL_DEFAULT_PRINTER_MODEL); } } } m_sequential_view.marker.init(filename); // initializes point sizes std::array point_sizes; ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); m_detected_point_sizes = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; // BBS initialzed view_type items m_user_mode = mode; update_by_mode(m_user_mode); m_layers_slider->init_texture(); m_gl_data_initialized = true; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished"); } void GCodeViewer::on_change_color_mode(bool is_dark) { m_is_dark = is_dark; m_sequential_view.marker.on_change_color_mode(m_is_dark); m_sequential_view.gcode_window.on_change_color_mode(m_is_dark); } void GCodeViewer::set_scale(float scale) { if(m_scale != scale)m_scale = scale; if (m_sequential_view.m_scale != scale) { m_sequential_view.m_scale = scale; m_sequential_view.marker.m_scale = scale; } } void GCodeViewer::update_by_mode(ConfigOptionMode mode) { view_type_items.clear(); view_type_items_str.clear(); options_items.clear(); // BBS initialzed view_type items view_type_items.push_back(EViewType::FeatureType); view_type_items.push_back(EViewType::ColorPrint); view_type_items.push_back(EViewType::Feedrate); view_type_items.push_back(EViewType::Height); view_type_items.push_back(EViewType::Width); view_type_items.push_back(EViewType::VolumetricRate); view_type_items.push_back(EViewType::LayerTime); view_type_items.push_back(EViewType::FanSpeed); view_type_items.push_back(EViewType::Temperature); //if (mode == ConfigOptionMode::comDevelop) { // view_type_items.push_back(EViewType::Tool); //} for (int i = 0; i < view_type_items.size(); i++) { view_type_items_str.push_back(get_view_type_string(view_type_items[i])); } // BBS for first layer inspection view_type_items.push_back(EViewType::FilamentId); options_items.push_back(EMoveType::Travel); options_items.push_back(EMoveType::Retract); options_items.push_back(EMoveType::Unretract); options_items.push_back(EMoveType::Wipe); //if (mode == ConfigOptionMode::comDevelop) { // options_items.push_back(EMoveType::Tool_change); //} //BBS: seam is not real move and extrusion, put at last line options_items.push_back(EMoveType::Seam); } std::vector GCodeViewer::get_plater_extruder() { return m_plater_extruder; } //BBS: always load shell at preview void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, const BuildVolume& build_volume, const std::vector& exclude_bounding_box, bool initialized, ConfigOptionMode mode, bool only_gcode) { // avoid processing if called with the same gcode_result if (m_last_result_id == gcode_result.id) { //BBS: add logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": the same id %1%, return directly, result %2% ") % m_last_result_id % (&gcode_result); return; } //BBS: add logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": gcode result %1%, new id %2%, gcode file %3% ") % (&gcode_result) % m_last_result_id % gcode_result.filename; // release gpu memory, if used reset(); //BBS: add mutex for protection of gcode result gcode_result.lock(); //BBS: add safe check if (gcode_result.moves.size() == 0) { //result cleaned before slicing ,should return here BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": gcode result reset before, return directly!"); gcode_result.unlock(); return; } //BBS: move the id to the end of reset m_last_result_id = gcode_result.id; m_gcode_result = &gcode_result; m_only_gcode_in_preview = only_gcode; m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends); //BBS: add only gcode mode //if (wxGetApp().is_gcode_viewer()) if (m_only_gcode_in_preview) m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; m_max_print_height = gcode_result.printable_height; load_toolpaths(gcode_result, build_volume, exclude_bounding_box); //BBS: add mutex for protection of gcode result if (m_layers.empty()) { gcode_result.unlock(); return; } m_settings_ids = gcode_result.settings_ids; m_filament_diameters = gcode_result.filament_diameters; m_filament_densities = gcode_result.filament_densities; //BBS: always load shell at preview /*if (wxGetApp().is_editor()) { //load_shells(print, initialized); } else {*/ //BBS: add only gcode mode if (m_only_gcode_in_preview) { Pointfs printable_area; //BBS: add bed exclude area Pointfs bed_exclude_area = Pointfs(); std::string texture; std::string model; if (!gcode_result.printable_area.empty()) { // bed shape detected in the gcode printable_area = gcode_result.printable_area; const auto bundle = wxGetApp().preset_bundle; if (bundle != nullptr && !m_settings_ids.printer.empty()) { const Preset* preset = bundle->printers.find_preset(m_settings_ids.printer); if (preset != nullptr) { model = PresetUtils::system_printer_bed_model(*preset); texture = PresetUtils::system_printer_bed_texture(*preset); } } //BBS: add bed exclude area if (!gcode_result.bed_exclude_area.empty()) bed_exclude_area = gcode_result.bed_exclude_area; wxGetApp().plater()->set_bed_shape(printable_area, bed_exclude_area, gcode_result.printable_height, texture, model, gcode_result.printable_area.empty()); } /*else { // adjust printbed size in dependence of toolpaths bbox const double margin = 10.0; const Vec2d min(m_paths_bounding_box.min.x() - margin, m_paths_bounding_box.min.y() - margin); const Vec2d max(m_paths_bounding_box.max.x() + margin, m_paths_bounding_box.max.y() + margin); const Vec2d size = max - min; printable_area = { { min.x(), min.y() }, { max.x(), min.y() }, { max.x(), min.y() + 0.442265 * size.y()}, { max.x() - 10.0, min.y() + 0.4711325 * size.y()}, { max.x() + 10.0, min.y() + 0.5288675 * size.y()}, { max.x(), min.y() + 0.557735 * size.y()}, { max.x(), max.y() }, { min.x() + 0.557735 * size.x(), max.y()}, { min.x() + 0.5288675 * size.x(), max.y() - 10.0}, { min.x() + 0.4711325 * size.x(), max.y() + 10.0}, { min.x() + 0.442265 * size.x(), max.y()}, { min.x(), max.y() } }; }*/ } m_print_statistics = gcode_result.print_statistics; if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { const float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; if (time == 0.0f || short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; } // set to color print by default if use multi extruders if (m_extruder_ids.size() > 1) { for (int i = 0; i < view_type_items.size(); i++) { if (view_type_items[i] == EViewType::ColorPrint) { m_view_type_sel = i; break; } } set_view_type(EViewType::ColorPrint); } m_fold = false; bool only_gcode_3mf = false; PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); bool current_has_print_instances = current_plate->has_printable_instances(); if (current_plate->is_slice_result_valid() && wxGetApp().model().objects.empty() && !current_has_print_instances) only_gcode_3mf = true; m_layers_slider->set_menu_enable(!(only_gcode || only_gcode_3mf)); m_layers_slider->set_as_dirty(); m_moves_slider->set_as_dirty(); //BBS m_conflict_result = gcode_result.conflict_result; if (m_conflict_result) { m_conflict_result.value().layer = m_layers.get_l_at(m_conflict_result.value()._height); } //BBS: add mutex for protection of gcode result gcode_result.unlock(); //BBS: add logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished, m_buffers size %1%!")%m_buffers.size(); } void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) { #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS //BBS: add mutex for protection of gcode result gcode_result.lock(); //BBS: add safe check if (gcode_result.moves.size() == 0) { //result cleaned before slicing ,should return here BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": gcode result reset before, return directly!"); gcode_result.unlock(); return; } //BBS: add mutex for protection of gcode result if (m_moves_count == 0) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": gcode result m_moves_count is 0, return directly!"); gcode_result.unlock(); return; } wxBusyCursor busy; if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) { // update tool colors from config stored in the gcode m_tools.m_tool_colors = decode_colors(gcode_result.extruder_colors); m_tools.m_tool_visibles = std::vector(m_tools.m_tool_colors.size()); for (auto item: m_tools.m_tool_visibles) item = true; } else { // update tool colors m_tools.m_tool_colors = decode_colors(str_tool_colors); m_tools.m_tool_visibles = std::vector(m_tools.m_tool_colors.size()); for (auto item : m_tools.m_tool_visibles) item = true; } for (int i = 0; i < m_tools.m_tool_colors.size(); i++) { m_tools.m_tool_colors[i] = adjust_color_for_rendering(m_tools.m_tool_colors[i]); } // ensure there are enough colors defined while (m_tools.m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) { m_tools.m_tool_colors.push_back(decode_color("#FF8000")); m_tools.m_tool_visibles.push_back(true); } // update ranges for coloring / legend m_extrusions.reset_ranges(); for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) continue; const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; switch (curr.type) { case EMoveType::Extrude: { m_extrusions.ranges.height.update_from(round_to_bin(curr.height)); m_extrusions.ranges.width.update_from(round_to_bin(curr.width)); m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); m_extrusions.ranges.temperature.update_from(curr.temperature); if (curr.extrusion_role != erCustom || is_visible(erCustom)) m_extrusions.ranges.volumetric_rate.update_from(round_to_bin(curr.volumetric_rate())); if (curr.layer_duration > 0.f) { m_extrusions.ranges.layer_duration.update_from(curr.layer_duration); } [[fallthrough]]; } case EMoveType::Travel: { if (m_buffers[buffer_id(curr.type)].visible) m_extrusions.ranges.feedrate.update_from(curr.feedrate); break; } default: { break; } } } #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS //BBS: add mutex for protection of gcode result gcode_result.unlock(); // update buffers' render paths refresh_render_paths(); log_memory_used("Refreshed G-code extrusion paths, "); } void GCodeViewer::refresh_render_paths() { refresh_render_paths(false, false); } void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) { if (config != nullptr) m_shells.volumes.update_colors_by_extruder(config,false); } //BBS: always load shell at preview void GCodeViewer::reset_shell() { m_shells.volumes.clear(); m_shells.print_id = -1; m_shell_bounding_box = BoundingBoxf3(); } void GCodeViewer::reset() { //BBS: should also reset the result id BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": current result id %1% ")%m_last_result_id; m_last_result_id = -1; //BBS: add only gcode mode m_only_gcode_in_preview = false; m_moves_count = 0; m_ssid_to_moveid_map.clear(); m_ssid_to_moveid_map.shrink_to_fit(); for (TBuffer& buffer : m_buffers) { buffer.reset(); } m_paths_bounding_box = BoundingBoxf3(); m_max_bounding_box = BoundingBoxf3(); m_max_print_height = 0.0f; m_tools.m_tool_colors = std::vector(); m_tools.m_tool_visibles = std::vector(); m_extruders_count = 0; m_extruder_ids = std::vector(); m_filament_diameters = std::vector(); m_filament_densities = std::vector(); m_extrusions.reset_ranges(); //BBS: always load shell at preview //m_shells.volumes.clear(); m_layers.reset(); m_layers_z_range = { 0, 0 }; m_roles = std::vector(); m_print_statistics.reset(); m_custom_gcode_per_print_z = std::vector(); m_sequential_view.gcode_window.reset(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); #endif // ENABLE_GCODE_VIEWER_STATISTICS m_contained_in_bed = true; } //BBS: GUI refactor: add canvas width and height void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin) { #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_opengl(); m_statistics.total_instances_gpu_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS //BBS: always render shells in preview window render_shells(); m_legend_height = 0.0f; if (m_roles.empty()) return; glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); //render_shells(); render_legend(m_legend_height, canvas_width, canvas_height, right_margin); if (m_user_mode != wxGetApp().get_mode()) { update_by_mode(wxGetApp().get_mode()); m_user_mode = wxGetApp().get_mode(); } //BBS fixed bottom_margin for space to render horiz slider int bottom_margin = 64; if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { m_sequential_view.marker.set_world_position(m_sequential_view.current_position); m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); //BBS fixed buttom margin. m_moves_slider.pos_y m_sequential_view.render(m_legend_height, canvas_width, canvas_height - bottom_margin * m_scale, right_margin * m_scale, m_view_type); } #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS //BBS render slider render_slider(canvas_width, canvas_height); } #define ENABLE_CALIBRATION_THUMBNAIL_OUTPUT 0 #if ENABLE_CALIBRATION_THUMBNAIL_OUTPUT static void debug_calibration_output_thumbnail(const ThumbnailData& thumbnail_data) { // debug export of generated image wxImage image(thumbnail_data.width, thumbnail_data.height); image.InitAlpha(); for (unsigned int r = 0; r < thumbnail_data.height; ++r) { unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width; for (unsigned int c = 0; c < thumbnail_data.width; ++c) { unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c); image.SetRGB((int)c, (int)r, px[0], px[1], px[2]); image.SetAlpha((int)c, (int)r, px[3]); } } image.SaveFile("D:/calibrate.png", wxBITMAP_TYPE_PNG); } #endif void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager) { int plate_idx = thumbnail_params.plate_id; PartPlate* plate = partplate_list.get_plate(plate_idx); BoundingBoxf3 plate_box = plate->get_bounding_box(false); plate_box.min.z() = 0.0; plate_box.max.z() = 0.0; Vec3d center = plate_box.center(); #if 1 Camera camera; camera.apply_viewport(0,0,thumbnail_data.width, thumbnail_data.height); camera.set_scene_box(plate_box); camera.set_type(Camera::EType::Ortho); camera.set_target(center); camera.select_view("top"); camera.apply_view_matrix(); camera.zoom_to_box(plate_box, 1.0f); camera.apply_projection(plate_box); auto render_as_triangles = [ #if ENABLE_GCODE_VIEWER_STATISTICS this #endif // ENABLE_GCODE_VIEWER_STATISTICS ](TBuffer &buffer, std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { const RenderPath& path = *it; // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(!path.sizes.empty()); assert(!path.offsets.empty()); glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } }; auto render_as_instanced_model = [ #if ENABLE_GCODE_VIEWER_STATISTICS this #endif // ENABLE_GCODE_VIEWER_STATISTICS ](TBuffer& buffer, GLShaderProgram& shader) { for (auto& range : buffer.model.instances.render_ranges.ranges) { if (range.vbo == 0 && range.count > 0) { glsafe(::glGenBuffers(1, &range.vbo)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo)); glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } if (range.vbo > 0) { buffer.model.model.set_color(-1, range.color); buffer.model.model.render_instanced(range.vbo, range.count); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_instanced_models_calls_count; m_statistics.total_instances_gpu_size += static_cast(range.count * buffer.model.instances.instance_size_bytes()); #endif // ENABLE_GCODE_VIEWER_STATISTICS } } }; #if ENABLE_GCODE_VIEWER_STATISTICS auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { #else auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { #endif // ENABLE_GCODE_VIEWER_STATISTICS struct Range { unsigned int first; unsigned int last; bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } }; Range buffer_range = { 0, 0 }; size_t indices_per_instance = buffer.model.data.indices_count(); for (size_t j = 0; j < buffer.indices.size(); ++j) { const IBuffer& i_buffer = buffer.indices[j]; buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); bool has_normals = buffer.vertices.normal_size_floats() > 0; if (has_normals) { glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); for (auto& range : buffer.model.instances.render_ranges.ranges) { Range range_range = { range.offset, range.offset + range.count }; if (range_range.intersects(buffer_range)) { shader.set_uniform("uniform_color", range.color); unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; if (count > 0) { glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_batched_models_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); if (has_normals) glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); buffer_range.first = buffer_range.last; } }; unsigned char begin_id = buffer_id(EMoveType::Retract); unsigned char end_id = buffer_id(EMoveType::Count); BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail: begin_id %1%, end_id %2%")%begin_id %end_id; for (unsigned char i = begin_id; i < end_id; ++i) { TBuffer& buffer = m_buffers[i]; if (!buffer.visible || !buffer.has_data()) continue; GLShaderProgram* shader = opengl_manager.get_shader("cali"); if (shader != nullptr) { shader->start_using(); if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { //shader->set_uniform("emission_factor", 0.25f); render_as_instanced_model(buffer, *shader); //shader->set_uniform("emission_factor", 0.0f); } else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { //shader->set_uniform("emission_factor", 0.25f); render_as_batched_model(buffer, *shader); //shader->set_uniform("emission_factor", 0.0f); } else { switch (buffer.render_primitive_type) { default: break; } int uniform_color = shader->get_uniform_location("uniform_color"); auto it_path = buffer.render_paths.begin(); for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast(buffer.indices.size()); ++ibuffer_id) { const IBuffer& i_buffer = buffer.indices[ibuffer_id]; // Skip all paths with ibuffer_id < ibuffer_id. for (; it_path != buffer.render_paths.end() && it_path->ibuffer_id < ibuffer_id; ++it_path); if (it_path == buffer.render_paths.end() || it_path->ibuffer_id > ibuffer_id) // Not found. This shall not happen. continue; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); bool has_normals = false;// buffer.vertices.normal_size_floats() > 0; if (has_normals) { glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors. switch (buffer.render_primitive_type) { case TBuffer::ERenderPrimitiveType::Triangle: { render_as_triangles(buffer, it_path, buffer.render_paths.end(), *shader, uniform_color); break; } default: { break; } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); if (has_normals) glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } } shader->stop_using(); } else { BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail: can not find shader"); } } #endif BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail: exit"); } void GCodeViewer::_render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager) { BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail prepare: width %1%, height %2%")%w %h; thumbnail_data.set(w, h); if (!thumbnail_data.is_valid()) return; //TODO bool multisample = m_multisample_allowed; bool multisample = OpenGLManager::can_multisample(); //if (!multisample) // glsafe(::glEnable(GL_MULTISAMPLE)); GLint max_samples; glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples)); GLsizei num_samples = max_samples / 2; GLuint render_fbo; glsafe(::glGenFramebuffers(1, &render_fbo)); glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail prepare: max_samples %1%, multisample %2%, render_fbo %3%")%max_samples %multisample %render_fbo; GLuint render_tex = 0; GLuint render_tex_buffer = 0; if (multisample) { // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 glsafe(::glGenRenderbuffers(1, &render_tex_buffer)); glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer)); glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h)); glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer)); } else { glsafe(::glGenTextures(1, &render_tex)); glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); } GLuint render_depth; glsafe(::glGenRenderbuffers(1, &render_depth)); glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); if (multisample) glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h)); else glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h)); glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; glsafe(::glDrawBuffers(1, drawBufs)); if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { _render_calibration_thumbnail_internal(thumbnail_data, thumbnail_params, partplate_list, opengl_manager); if (multisample) { GLuint resolve_fbo; glsafe(::glGenFramebuffers(1, &resolve_fbo)); glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo)); GLuint resolve_tex; glsafe(::glGenTextures(1, &resolve_tex)); glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0)); glsafe(::glDrawBuffers(1, drawBufs)); if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo)); glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo)); glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo)); glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); } glsafe(::glDeleteTextures(1, &resolve_tex)); glsafe(::glDeleteFramebuffers(1, &resolve_fbo)); } else glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); } #if ENABLE_CALIBRATION_THUMBNAIL_OUTPUT debug_calibration_output_thumbnail(thumbnail_data); #endif glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); glsafe(::glDeleteRenderbuffers(1, &render_depth)); if (render_tex_buffer != 0) glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer)); if (render_tex != 0) glsafe(::glDeleteTextures(1, &render_tex)); glsafe(::glDeleteFramebuffers(1, &render_fbo)); //if (!multisample) // glsafe(::glDisable(GL_MULTISAMPLE)); BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail prepare: exit"); } //BBS void GCodeViewer::render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager) { // reset values and refresh render int last_view_type_sel = m_view_type_sel; EViewType last_view_type = m_view_type; unsigned int last_role_visibility_flags = m_extrusions.role_visibility_flags; // set color scheme to FilamentId for (int i = 0; i < view_type_items.size(); i++) { if (view_type_items[i] == EViewType::FilamentId) { m_view_type_sel = i; break; } } set_view_type(EViewType::FilamentId, false); // set m_layers_z_range to 0, 1; // To be safe, we include both layers here although layer 1 seems enough // layer 0: custom extrusions such as flow calibration etc. // layer 1: the real first layer of object std::array tmp_layers_z_range = m_layers_z_range; m_layers_z_range = {0, 1}; // BBS exclude feature types m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags & ~(1 << erSkirt); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags & ~(1 << erCustom); // BBS include feature types m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erWipeTower); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erPerimeter); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erExternalPerimeter); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erOverhangPerimeter); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erSolidInfill); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erTopSolidInfill); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erInternalInfill); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erBottomSurface); refresh_render_paths(false, false); _render_calibration_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, partplate_list, opengl_manager); // restore values and refresh render // reset m_layers_z_range and view type m_view_type_sel = last_view_type_sel; set_view_type(last_view_type, false); m_layers_z_range = tmp_layers_z_range; m_extrusions.role_visibility_flags = last_role_visibility_flags; refresh_render_paths(false, false); } bool GCodeViewer::can_export_toolpaths() const { return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle; } void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last) { auto is_visible = [this](unsigned int id) { for (const TBuffer &buffer : m_buffers) { if (buffer.visible) { for (const Path &path : buffer.paths) { if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) return true; } } } return false; }; const int first_diff = static_cast(first) - static_cast(m_sequential_view.last_current.first); const int last_diff = static_cast(last) - static_cast(m_sequential_view.last_current.last); unsigned int new_first = first; unsigned int new_last = last; if (m_sequential_view.skip_invisible_moves) { while (!is_visible(new_first)) { if (first_diff > 0) ++new_first; else --new_first; } while (!is_visible(new_last)) { if (last_diff > 0) ++new_last; else --new_last; } } m_sequential_view.current.first = new_first; m_sequential_view.current.last = new_last; m_sequential_view.last_current = m_sequential_view.current; refresh_render_paths(true, true); if (new_first != first || new_last != last) { update_moves_slider(); } } void GCodeViewer::enable_moves_slider(bool enable) const { bool render_as_disabled = !enable; if (m_moves_slider != nullptr && m_moves_slider->is_rendering_as_disabled() != render_as_disabled) { m_moves_slider->set_render_as_disabled(render_as_disabled); m_moves_slider->set_as_dirty(); } } void GCodeViewer::update_moves_slider(bool set_to_max) { const GCodeViewer::SequentialView &view = get_sequential_view(); // this should not be needed, but it is here to try to prevent rambling crashes on Mac Asan if (view.endpoints.last < view.endpoints.first) return; std::vector values(view.endpoints.last - view.endpoints.first + 1); std::vector alternate_values(view.endpoints.last - view.endpoints.first + 1); unsigned int count = 0; for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i) { values[count] = static_cast(i + 1); if (view.gcode_ids[i] > 0) alternate_values[count] = static_cast(view.gcode_ids[i]); ++count; } m_moves_slider->SetSliderValues(values); m_moves_slider->SetSliderAlternateValues(alternate_values); m_moves_slider->SetMaxValue(view.endpoints.last - view.endpoints.first); m_moves_slider->SetSelectionSpan(view.current.first - view.endpoints.first, view.current.last - view.endpoints.first); if (set_to_max) m_moves_slider->SetHigherValue(m_moves_slider->GetMaxValue()); } void GCodeViewer::update_layers_slider_mode() { // true -> single-extruder printer profile OR // multi-extruder printer profile , but whole model is printed by only one extruder // false -> multi-extruder printer profile , and model is printed by several extruders bool one_extruder_printed_model = true; // extruder used for whole model for multi-extruder printer profile int only_extruder = -1; // BBS if (wxGetApp().filaments_cnt() > 1) { const ModelObjectPtrs &objects = wxGetApp().plater()->model().objects; // check if whole model uses just only one extruder if (!objects.empty()) { const int extruder = objects[0]->config.has("extruder") ? objects[0]->config.option("extruder")->getInt() : 0; auto is_one_extruder_printed_model = [objects, extruder]() { for (ModelObject *object : objects) { if (object->config.has("extruder") && object->config.option("extruder")->getInt() != extruder) return false; for (ModelVolume *volume : object->volumes) if ((volume->config.has("extruder") && volume->config.option("extruder")->getInt() != extruder) || !volume->mmu_segmentation_facets.empty()) return false; for (const auto &range : object->layer_config_ranges) if (range.second.has("extruder") && range.second.option("extruder")->getInt() != extruder) return false; } return true; }; if (is_one_extruder_printed_model()) only_extruder = extruder; else one_extruder_printed_model = false; } } // TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); } void GCodeViewer::update_marker_curr_move() { if ((int)m_last_result_id != -1) { auto it = std::find_if(m_gcode_result->moves.begin(), m_gcode_result->moves.end(), [this](auto move) { if (m_sequential_view.current.last < m_sequential_view.gcode_ids.size() && m_sequential_view.current.last >= 0) { return move.gcode_id == static_cast(m_sequential_view.gcode_ids[m_sequential_view.current.last]); } return false; }); if (it != m_gcode_result->moves.end()) m_sequential_view.marker.update_curr_move(*it); } } bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const { size_t id = static_cast(buffer_id(type)); return (id < m_buffers.size()) ? m_buffers[id].visible : false; } void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible) { size_t id = static_cast(buffer_id(type)); if (id < m_buffers.size()) m_buffers[id].visible = visible; } unsigned int GCodeViewer::get_options_visibility_flags() const { auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { return active ? (flags | (1 << flag)) : flags; }; unsigned int flags = 0; flags = set_flag(flags, static_cast(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel)); flags = set_flag(flags, static_cast(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe)); flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract)); flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract)); flags = set_flag(flags, static_cast(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam)); flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change)); flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change)); flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print)); flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); return flags; } void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) { auto is_flag_set = [flags](unsigned int flag) { return (flags & (1 << flag)) != 0; }; set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast(Preview::OptionType::Travel))); set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast(Preview::OptionType::Wipe))); set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast(Preview::OptionType::Seams))); set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) { bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0]; bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1]; m_layers_z_range = layers_z_range; refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); update_moves_slider(true); } void GCodeViewer::export_toolpaths_to_obj(const char* filename) const { if (filename == nullptr) return; if (!has_data()) return; wxBusyCursor busy; // the data needed is contained into the Extrude TBuffer const TBuffer& t_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; if (!t_buffer.has_data()) return; if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) return; // collect color information to generate materials std::vector colors; for (const RenderPath& path : t_buffer.render_paths) { colors.push_back(path.color); } sort_remove_duplicates(colors); // save materials file boost::filesystem::path mat_filename(filename); mat_filename.replace_extension("mtl"); CNumericLocalesSetter locales_setter; FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; return; } fprintf(fp, "# G-Code Toolpaths Materials\n"); fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); unsigned int colors_count = 1; for (const Color& color : colors) { fprintf(fp, "\nnewmtl material_%d\n", colors_count++); fprintf(fp, "Ka 1 1 1\n"); fprintf(fp, "Kd %g %g %g\n", color[0], color[1], color[2]); fprintf(fp, "Ks 0 0 0\n"); } fclose(fp); // save geometry file fp = boost::nowide::fopen(filename, "w"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; return; } fprintf(fp, "# G-Code Toolpaths\n"); fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); std::vector out_vertices; std::vector out_normals; struct VerticesOffset { unsigned int vbo; size_t offset; }; std::vector vertices_offsets; vertices_offsets.push_back({ t_buffer.vertices.vbos.front(), 0 }); // get vertices/normals data from vertex buffers on gpu for (size_t i = 0; i < t_buffer.vertices.vbos.size(); ++i) { const size_t floats_count = t_buffer.vertices.sizes[i] / sizeof(float); VertexBuffer vertices(floats_count); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.vbos[i])); glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(t_buffer.vertices.sizes[i]), static_cast(vertices.data()))); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); const size_t vertices_count = floats_count / floats_per_vertex; for (size_t j = 0; j < vertices_count; ++j) { const size_t base = j * floats_per_vertex; out_vertices.push_back({ vertices[base + 0], vertices[base + 1], vertices[base + 2] }); out_normals.push_back({ vertices[base + 3], vertices[base + 4], vertices[base + 5] }); } if (i < t_buffer.vertices.vbos.size() - 1) vertices_offsets.push_back({ t_buffer.vertices.vbos[i + 1], vertices_offsets.back().offset + vertices_count }); } // save vertices to file fprintf(fp, "\n# vertices\n"); for (const Vec3f& v : out_vertices) { fprintf(fp, "v %g %g %g\n", v.x(), v.y(), v.z()); } // save normals to file fprintf(fp, "\n# normals\n"); for (const Vec3f& n : out_normals) { fprintf(fp, "vn %g %g %g\n", n.x(), n.y(), n.z()); } size_t i = 0; for (const Color& color : colors) { // save material triangles to file fprintf(fp, "\nusemtl material_%zu\n", i + 1); fprintf(fp, "# triangles material %zu\n", i + 1); for (const RenderPath& render_path : t_buffer.render_paths) { if (render_path.color != color) continue; const IBuffer& ibuffer = t_buffer.indices[render_path.ibuffer_id]; size_t vertices_offset = 0; for (size_t j = 0; j < vertices_offsets.size(); ++j) { const VerticesOffset& offset = vertices_offsets[j]; if (offset.vbo == ibuffer.vbo) { vertices_offset = offset.offset; break; } } // get indices data from index buffer on gpu glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.ibo)); for (size_t j = 0; j < render_path.sizes.size(); ++j) { IndexBuffer indices(render_path.sizes[j]); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(render_path.offsets[j]), static_cast(render_path.sizes[j] * sizeof(IBufferType)), static_cast(indices.data()))); const size_t triangles_count = render_path.sizes[j] / 3; for (size_t k = 0; k < triangles_count; ++k) { const size_t base = k * 3; const size_t v1 = 1 + static_cast(indices[base + 0]) + vertices_offset; const size_t v2 = 1 + static_cast(indices[base + 1]) + vertices_offset; const size_t v3 = 1 + static_cast(indices[base + 2]) + vertices_offset; if (v1 != v2) // do not export dummy triangles fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", v1, v1, v2, v2, v3, v3); } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } ++i; } fclose(fp); } void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const BuildVolume& build_volume, const std::vector& exclude_bounding_box) { // max index buffer size, in bytes static const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024; //BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(",build_volume center{%1%, %2%}, moves count %3%\n")%build_volume.bed_center().x() % build_volume.bed_center().y() %gcode_result.moves.size(); auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { int64_t vertices_size = 0; for (const MultiVertexBuffer& buffers : vertices) { for (const VertexBuffer& buffer : buffers) { vertices_size += SLIC3R_STDVEC_MEMSIZE(buffer, float); } //BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format("vertices count %1%\n")%buffers.size(); } int64_t indices_size = 0; for (const MultiIndexBuffer& buffers : indices) { for (const IndexBuffer& buffer : buffers) { indices_size += SLIC3R_STDVEC_MEMSIZE(buffer, IBufferType); } //BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format("indices count %1%\n")%buffers.size(); } log_memory_used(label, vertices_size + indices_size); }; // format data into the buffers to be rendered as points auto add_vertices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { vertices.push_back(curr.position.x()); vertices.push_back(curr.position.y()); vertices.push_back(curr.position.z()); }; auto add_indices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { buffer.add_path(curr, ibuffer_id, indices.size(), move_id); indices.push_back(static_cast(indices.size())); }; // format data into the buffers to be rendered as lines auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { auto add_vertex = [&vertices](const Vec3f& position, const Vec3f& normal) { // add position vertices.push_back(position.x()); vertices.push_back(position.y()); vertices.push_back(position.z()); // add normal vertices.push_back(normal.x()); vertices.push_back(normal.y()); vertices.push_back(normal.z()); }; // x component of the normal to the current segment (the normal is parallel to the XY plane) //BBS: Has modified a lot for this function to support arc move size_t loop_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() : 0; for (size_t i = 0; i < loop_num + 1; i++) { const Vec3f &previous = (i == 0? prev.position : curr.interpolation_points[i-1]); const Vec3f ¤t = (i == loop_num? curr.position : curr.interpolation_points[i]); const Vec3f dir = (current - previous).normalized(); Vec3f normal(dir.y(), -dir.x(), 0.0); normal.normalize(); // add previous vertex add_vertex(previous, normal); // add current vertex add_vertex(current, normal); } }; //BBS: modify a lot to support arc travel auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); buffer.paths.back().sub_paths.front().first.position = prev.position; } Path& last_path = buffer.paths.back(); size_t loop_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() : 0; for (size_t i = 0; i < loop_num + 1; i++) { //BBS: add previous index indices.push_back(static_cast(indices.size())); //BBS: add current index indices.push_back(static_cast(indices.size())); vbuffer_size += buffer.max_vertices_per_segment(); } last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; }; // format data into the buffers to be rendered as solid. auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { // append position vertices.push_back(position.x()); vertices.push_back(position.y()); vertices.push_back(position.z()); // append normal vertices.push_back(normal.x()); vertices.push_back(normal.y()); vertices.push_back(normal.z()); }; if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1); buffer.paths.back().sub_paths.back().first.position = prev.position; } Path& last_path = buffer.paths.back(); //BBS: Has modified a lot for this function to support arc move size_t loop_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() : 0; for (size_t i = 0; i < loop_num + 1; i++) { const Vec3f &prev_position = (i == 0? prev.position : curr.interpolation_points[i-1]); const Vec3f &curr_position = (i == loop_num? curr.position : curr.interpolation_points[i]); const Vec3f dir = (curr_position - prev_position).normalized(); const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); const Vec3f left = -right; const Vec3f up = right.cross(dir); const Vec3f down = -up; const float half_width = 0.5f * last_path.width; const float half_height = 0.5f * last_path.height; const Vec3f prev_pos = prev_position - half_height * up; const Vec3f curr_pos = curr_position - half_height * up; const Vec3f d_up = half_height * up; const Vec3f d_down = -half_height * up; const Vec3f d_right = half_width * right; const Vec3f d_left = -half_width * right; if ((last_path.vertices_count() == 1 || vertices.empty()) && i == 0) { store_vertex(vertices, prev_pos + d_up, up); store_vertex(vertices, prev_pos + d_right, right); store_vertex(vertices, prev_pos + d_down, down); store_vertex(vertices, prev_pos + d_left, left); } else { store_vertex(vertices, prev_pos + d_right, right); store_vertex(vertices, prev_pos + d_left, left); } store_vertex(vertices, curr_pos + d_up, up); store_vertex(vertices, curr_pos + d_right, right); store_vertex(vertices, curr_pos + d_down, down); store_vertex(vertices, curr_pos + d_left, left); } last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; }; auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { static Vec3f prev_dir; static Vec3f prev_up; static float sq_prev_length; auto store_triangle = [](IndexBuffer& indices, IBufferType i1, IBufferType i2, IBufferType i3) { indices.push_back(i1); indices.push_back(i2); indices.push_back(i3); }; auto append_dummy_cap = [store_triangle](IndexBuffer& indices, IBufferType id) { store_triangle(indices, id, id, id); store_triangle(indices, id, id, id); }; auto convert_vertices_offset = [](size_t vbuffer_size, const std::array& v_offsets) { std::array ret = { static_cast(static_cast(vbuffer_size) + v_offsets[0]), static_cast(static_cast(vbuffer_size) + v_offsets[1]), static_cast(static_cast(vbuffer_size) + v_offsets[2]), static_cast(static_cast(vbuffer_size) + v_offsets[3]), static_cast(static_cast(vbuffer_size) + v_offsets[4]), static_cast(static_cast(vbuffer_size) + v_offsets[5]), static_cast(static_cast(vbuffer_size) + v_offsets[6]), static_cast(static_cast(vbuffer_size) + v_offsets[7]) }; return ret; }; auto append_starting_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { store_triangle(indices, v_offsets[0], v_offsets[2], v_offsets[1]); store_triangle(indices, v_offsets[0], v_offsets[3], v_offsets[2]); }; auto append_stem_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { store_triangle(indices, v_offsets[0], v_offsets[1], v_offsets[4]); store_triangle(indices, v_offsets[1], v_offsets[5], v_offsets[4]); store_triangle(indices, v_offsets[1], v_offsets[2], v_offsets[5]); store_triangle(indices, v_offsets[2], v_offsets[6], v_offsets[5]); store_triangle(indices, v_offsets[2], v_offsets[3], v_offsets[6]); store_triangle(indices, v_offsets[3], v_offsets[7], v_offsets[6]); store_triangle(indices, v_offsets[3], v_offsets[0], v_offsets[7]); store_triangle(indices, v_offsets[0], v_offsets[4], v_offsets[7]); }; auto append_ending_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]); store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); }; if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); buffer.paths.back().sub_paths.back().first.position = prev.position; } Path& last_path = buffer.paths.back(); bool is_first_segment = (last_path.vertices_count() == 1); //BBS: has modified a lot for this function to support arc move std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); size_t loop_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() : 0; for (size_t i = 0; i < loop_num + 1; i++) { const Vec3f &prev_position = (i == 0? prev.position : curr.interpolation_points[i-1]); const Vec3f &curr_position = (i == loop_num? curr.position : curr.interpolation_points[i]); const Vec3f dir = (curr_position - prev_position).normalized(); const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); const Vec3f up = right.cross(dir); const float sq_length = (curr_position - prev_position).squaredNorm(); if ((is_first_segment || vbuffer_size == 0) && i == 0) { if (is_first_segment && i == 0) // starting cap triangles append_starting_cap_triangles(indices, first_seg_v_offsets); // dummy triangles outer corner cap append_dummy_cap(indices, vbuffer_size); // stem triangles append_stem_triangles(indices, first_seg_v_offsets); vbuffer_size += 8; } else { float displacement = 0.0f; float cos_dir = prev_dir.dot(dir); if (cos_dir > -0.9998477f) { // if the angle between adjacent segments is smaller than 179 degrees const Vec3f med_dir = (prev_dir + dir).normalized(); const float half_width = 0.5f * last_path.width; displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); } float sq_displacement = sqr(displacement); bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length&& sq_displacement < sq_length; bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; // whether the angle between adjacent segments is greater than 45 degrees bool is_sharp = cos_dir < 0.7071068f; bool right_displaced = false; bool left_displaced = false; if (!is_sharp && can_displace) { if (is_right_turn) left_displaced = true; else right_displaced = true; } // triangles outer corner cap if (is_right_turn) { if (left_displaced) // dummy triangles append_dummy_cap(indices, vbuffer_size); else { store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 1); store_triangle(indices, vbuffer_size + 1, vbuffer_size - 2, vbuffer_size - 1); } } else { if (right_displaced) // dummy triangles append_dummy_cap(indices, vbuffer_size); else { store_triangle(indices, vbuffer_size - 4, vbuffer_size - 3, vbuffer_size + 0); store_triangle(indices, vbuffer_size - 3, vbuffer_size - 2, vbuffer_size + 0); } } // stem triangles non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); append_stem_triangles(indices, non_first_seg_v_offsets); vbuffer_size += 6; } prev_dir = dir; prev_up = up; sq_prev_length = sq_length; } if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) // ending cap triangles append_ending_cap_triangles(indices, (is_first_segment && !curr.is_arc_move_with_interpolation_points()) ? first_seg_v_offsets : non_first_seg_v_offsets); last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; }; // format data into the buffers to be rendered as instanced model auto add_model_instance = [](const GCodeProcessorResult::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { // append position instances.push_back(curr.position.x()); instances.push_back(curr.position.y()); instances.push_back(curr.position.z()); // append width instances.push_back(curr.width); // append height instances.push_back(curr.height); // append id instances_ids.push_back(move_id); }; // format data into the buffers to be rendered as batched model auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { const double width = static_cast(1.5f * curr.width); const double height = static_cast(1.5f * curr.height); const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast(), Vec3d::Zero(), { width, width, height }); const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); for (const auto& entity : data.entities) { // append vertices for (size_t i = 0; i < entity.positions.size(); ++i) { // append position const Vec3d position = trafo * entity.positions[i].cast(); vertices.push_back(static_cast(position.x())); vertices.push_back(static_cast(position.y())); vertices.push_back(static_cast(position.z())); // append normal const Vec3d normal = normal_matrix * entity.normals[i].cast(); vertices.push_back(static_cast(normal.x())); vertices.push_back(static_cast(normal.y())); vertices.push_back(static_cast(normal.z())); } } // append instance position instances.push_back(curr.position.x()); instances.push_back(curr.position.y()); instances.push_back(curr.position.z()); // append instance id instances_ids.push_back(move_id); }; auto add_indices_as_model_batch = [](const GLModel::InitializationData& data, IndexBuffer& indices, IBufferType base_index) { for (const auto& entity : data.entities) { for (size_t i = 0; i < entity.indices.size(); ++i) { indices.push_back(static_cast(entity.indices[i] + base_index)); } } }; #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessorResult::MoveVertex); m_statistics.results_time = gcode_result.time; #endif // ENABLE_GCODE_VIEWER_STATISTICS m_moves_count = gcode_result.moves.size(); if (m_moves_count == 0) return; m_extruders_count = gcode_result.extruders_count; unsigned int progress_count = 0; static const unsigned int progress_threshold = 1000; //BBS: add only gcode mode ProgressDialog * progress_dialog = m_only_gcode_in_preview ? new ProgressDialog(_L("Loading G-codes"), "...", 100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; wxBusyCursor busy; //BBS: use convex_hull for toolpath outside check Points pts; // extract approximate paths bounding box from result //BBS: add only gcode mode for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { //if (wxGetApp().is_gcode_viewer()) { //if (m_only_gcode_in_preview) { // for the gcode viewer we need to take in account all moves to correctly size the printbed // m_paths_bounding_box.merge(move.position.cast()); //} //else { if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) { m_paths_bounding_box.merge(move.position.cast()); //BBS: use convex_hull for toolpath outside check pts.emplace_back(Point(scale_(move.position.x()), scale_(move.position.y()))); } //} } // BBS: also merge the point on arc to bounding box for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { // continue if not arc path if (!move.is_arc_move_with_interpolation_points()) continue; //if (wxGetApp().is_gcode_viewer()) //if (m_only_gcode_in_preview) // for (int i = 0; i < move.interpolation_points.size(); i++) // m_paths_bounding_box.merge(move.interpolation_points[i].cast()); //else { if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) for (int i = 0; i < move.interpolation_points.size(); i++) { m_paths_bounding_box.merge(move.interpolation_points[i].cast()); //BBS: use convex_hull for toolpath outside check pts.emplace_back(Point(scale_(move.interpolation_points[i].x()), scale_(move.interpolation_points[i].y()))); } //} } // set approximate max bounding box (take in account also the tool marker) m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(",m_paths_bounding_box {%1%, %2%}-{%3%, %4%}\n") %m_paths_bounding_box.min.x() %m_paths_bounding_box.min.y() %m_paths_bounding_box.max.x() %m_paths_bounding_box.max.y(); //if (wxGetApp().is_editor()) { //BBS: use convex_hull for toolpath outside check m_contained_in_bed = build_volume.all_paths_inside(gcode_result, m_paths_bounding_box); if (m_contained_in_bed) { //PartPlateList& partplate_list = wxGetApp().plater()->get_partplate_list(); //PartPlate* plate = partplate_list.get_curr_plate(); //const std::vector& exclude_bounding_box = plate->get_exclude_areas(); if (exclude_bounding_box.size() > 0) { int index; Slic3r::Polygon convex_hull_2d = Slic3r::Geometry::convex_hull(std::move(pts)); for (index = 0; index < exclude_bounding_box.size(); index ++) { Slic3r::Polygon p = exclude_bounding_box[index].polygon(true); // instance convex hull is scaled, so we need to scale here if (intersection({ p }, { convex_hull_2d }).empty() == false) { m_contained_in_bed = false; break; } } } } (const_cast(gcode_result)).toolpath_outside = !m_contained_in_bed; } m_sequential_view.gcode_ids.clear(); for (size_t i = 0; i < gcode_result.moves.size(); ++i) { const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; if (move.type != EMoveType::Seam) m_sequential_view.gcode_ids.push_back(move.gcode_id); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(",m_contained_in_bed %1%\n")%m_contained_in_bed; std::vector vertices(m_buffers.size()); std::vector indices(m_buffers.size()); std::vector instances(m_buffers.size()); std::vector instances_ids(m_buffers.size()); std::vector instances_offsets(m_buffers.size()); std::vector options_zs; size_t seams_count = 0; std::vector biased_seams_ids; // toolpaths data -> extract vertices from result for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; if (curr.type == EMoveType::Seam) { ++seams_count; biased_seams_ids.push_back(i - biased_seams_ids.size() - 1); } size_t move_id = i - seams_count; // skip first vertex if (i == 0) continue; const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; // update progress dialog ++progress_count; if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), _L("Generating geometry vertex data") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); progress_dialog->Fit(); progress_count = 0; } const unsigned char id = buffer_id(curr.type); TBuffer& t_buffer = m_buffers[id]; MultiVertexBuffer& v_multibuffer = vertices[id]; InstanceBuffer& inst_buffer = instances[id]; InstanceIdBuffer& inst_id_buffer = instances_ids[id]; InstancesOffsets& inst_offsets = instances_offsets[id]; /*if (i%1000 == 1) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":i=%1%, buffer_id %2% render_type %3%, gcode_id %4%\n") %i %(int)id %(int)t_buffer.render_primitive_type %curr.gcode_id; }*/ // ensure there is at least one vertex buffer if (v_multibuffer.empty()) v_multibuffer.push_back(VertexBuffer()); // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer // add another vertex buffer // BBS: get the point number and then judge whether the remaining buffer is enough size_t points_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() + 1 : 1; size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : points_num * t_buffer.max_vertices_per_segment_size_bytes(); if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { v_multibuffer.push_back(VertexBuffer()); if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { Path& last_path = t_buffer.paths.back(); if (prev.type == curr.type && last_path.matches(curr)) last_path.add_sub_path(prev, static_cast(v_multibuffer.size()) - 1, 0, move_id - 1); } } VertexBuffer& v_buffer = v_multibuffer.back(); switch (t_buffer.render_primitive_type) { case TBuffer::ERenderPrimitiveType::Point: { add_vertices_as_point(curr, v_buffer); break; } case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id); break; } case TBuffer::ERenderPrimitiveType::InstancedModel: { add_model_instance(curr, inst_buffer, inst_id_buffer, move_id); inst_offsets.push_back(prev.position - curr.position); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.instances_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS break; } case TBuffer::ERenderPrimitiveType::BatchedModel: { add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, move_id); inst_offsets.push_back(prev.position - curr.position); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.batched_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS break; } } // collect options zs for later use if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) options_zs.emplace_back(curr.position[2]); } } /*for (size_t b = 0; b < vertices.size(); ++b) { MultiVertexBuffer& v_multibuffer = vertices[b]; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":b=%1%, vertex buffer count %2%\n") %b %v_multibuffer.size(); }*/ auto extract_move_id = [&biased_seams_ids](size_t id) { size_t new_id = size_t(-1); auto it = std::lower_bound(biased_seams_ids.begin(), biased_seams_ids.end(), id); if (it == biased_seams_ids.end()) new_id = id + biased_seams_ids.size(); else { if (it == biased_seams_ids.begin() && *it < id) new_id = id; else if (it != biased_seams_ids.begin()) new_id = id + std::distance(biased_seams_ids.begin(), it); } return (new_id == size_t(-1)) ? id : new_id; }; //BBS: generate map from ssid to move id in advance to reduce computation m_ssid_to_moveid_map.clear(); m_ssid_to_moveid_map.reserve( m_moves_count - biased_seams_ids.size()); for (size_t i = 0; i < m_moves_count - biased_seams_ids.size(); i++) m_ssid_to_moveid_map.push_back(extract_move_id(i)); //BBS: smooth toolpaths corners for the given TBuffer using triangles auto smooth_triangle_toolpaths_corners = [&gcode_result, this](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) { auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) { return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]); }; auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) { vertices[offset + 0] = position.x(); vertices[offset + 1] = position.y(); vertices[offset + 2] = position.z(); }; auto match_right_vertices_with_internal_point = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, size_t curr_s_id, bool is_internal_point, size_t interpolation_point_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { if (&prev_sub_path == &next_sub_path || is_internal_point) { // previous and next segment are both contained into to the same vertex buffer VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; // offset into the vertex buffer of the next segment 1st vertex size_t temp_offset = prev_sub_path.last.s_id - curr_s_id; for (size_t i = prev_sub_path.last.s_id; i > curr_s_id; i--) { size_t move_id = m_ssid_to_moveid_map[i]; temp_offset += (gcode_result.moves[move_id].is_arc_move() ? gcode_result.moves[move_id].interpolation_points.size() : 0); } if (is_internal_point) { size_t move_id = m_ssid_to_moveid_map[curr_s_id]; temp_offset += (gcode_result.moves[move_id].interpolation_points.size() - interpolation_point_id); } const size_t next_1st_offset = temp_offset * 6 * vertex_size_floats; // offset into the vertex buffer of the right vertex of the previous segment const size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats; // new position of the right vertices const Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec; // update previous segment update_position_at(vbuffer, prev_right_offset, shared_vertex); // offset into the vertex buffer of the right vertex of the next segment const size_t next_right_offset = prev_sub_path.last.i_id - next_1st_offset; // update next segment update_position_at(vbuffer, next_right_offset, shared_vertex); } else { // previous and next segment are contained into different vertex buffers VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; // offset into the previous vertex buffer of the right vertex of the previous segment const size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats; // new position of the right vertices const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec; // update previous segment update_position_at(prev_vbuffer, prev_right_offset, shared_vertex); // offset into the next vertex buffer of the right vertex of the next segment const size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats; // update next segment update_position_at(next_vbuffer, next_right_offset, shared_vertex); } }; //BBS: modify a lot of this function to support arc move auto match_left_vertices_with_internal_point = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, size_t curr_s_id, bool is_internal_point, size_t interpolation_point_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { if (&prev_sub_path == &next_sub_path || is_internal_point) { // previous and next segment are both contained into to the same vertex buffer VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; // offset into the vertex buffer of the next segment 1st vertex size_t temp_offset = prev_sub_path.last.s_id - curr_s_id; for (size_t i = prev_sub_path.last.s_id; i > curr_s_id; i--) { size_t move_id = m_ssid_to_moveid_map[i]; temp_offset += (gcode_result.moves[move_id].is_arc_move() ? gcode_result.moves[move_id].interpolation_points.size() : 0); } if (is_internal_point) { size_t move_id = m_ssid_to_moveid_map[curr_s_id]; temp_offset += (gcode_result.moves[move_id].interpolation_points.size() - interpolation_point_id); } const size_t next_1st_offset = temp_offset * 6 * vertex_size_floats; // offset into the vertex buffer of the left vertex of the previous segment const size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats; // new position of the left vertices const Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec; // update previous segment update_position_at(vbuffer, prev_left_offset, shared_vertex); // offset into the vertex buffer of the left vertex of the next segment const size_t next_left_offset = prev_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats; // update next segment update_position_at(vbuffer, next_left_offset, shared_vertex); } else { // previous and next segment are contained into different vertex buffers VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; // offset into the previous vertex buffer of the left vertex of the previous segment const size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats; // new position of the left vertices const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec; // update previous segment update_position_at(prev_vbuffer, prev_left_offset, shared_vertex); // offset into the next vertex buffer of the left vertex of the next segment const size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats; // update next segment update_position_at(next_vbuffer, next_left_offset, shared_vertex); } }; size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats(); for (const Path& path : t_buffer.paths) { //BBS: the two segments of the path sharing the current vertex may belong //to two different vertex buffers size_t prev_sub_path_id = 0; size_t next_sub_path_id = 0; const size_t path_vertices_count = path.vertices_count(); const float half_width = 0.5f * path.width; // BBS: modify a lot to support arc move which has internal points for (size_t j = 1; j < path_vertices_count; ++j) { size_t curr_s_id = path.sub_paths.front().first.s_id + j; size_t move_id = m_ssid_to_moveid_map[curr_s_id]; int interpolation_points_num = gcode_result.moves[move_id].is_arc_move_with_interpolation_points()? gcode_result.moves[move_id].interpolation_points.size() : 0; int loop_num = interpolation_points_num; //BBS: select the subpaths which contains the previous/next segments if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id)) ++prev_sub_path_id; if (j == path_vertices_count - 1) { if (!gcode_result.moves[move_id].is_arc_move_with_interpolation_points()) break; // BBS: the last move has no internal point. loop_num--; //BBS: don't need to handle the endpoint of the last arc move of path next_sub_path_id = prev_sub_path_id; } else { if (!path.sub_paths[next_sub_path_id].contains(curr_s_id + 1)) ++next_sub_path_id; } const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id]; const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id]; // BBS: smooth triangle toolpaths corners including arc move which has internal interpolation point for (int k = 0; k <= loop_num; k++) { const Vec3f& prev = k==0? gcode_result.moves[move_id - 1].position : gcode_result.moves[move_id].interpolation_points[k-1]; const Vec3f& curr = k==interpolation_points_num? gcode_result.moves[move_id].position : gcode_result.moves[move_id].interpolation_points[k]; const Vec3f& next = k < interpolation_points_num - 1? gcode_result.moves[move_id].interpolation_points[k+1]: (k == interpolation_points_num - 1? gcode_result.moves[move_id].position : (gcode_result.moves[move_id + 1].is_arc_move_with_interpolation_points()? gcode_result.moves[move_id + 1].interpolation_points[0] : gcode_result.moves[move_id + 1].position)); const Vec3f prev_dir = (curr - prev).normalized(); const Vec3f prev_right = Vec3f(prev_dir.y(), -prev_dir.x(), 0.0f).normalized(); const Vec3f prev_up = prev_right.cross(prev_dir); const Vec3f next_dir = (next - curr).normalized(); const bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f; const float cos_dir = prev_dir.dot(next_dir); // whether the angle between adjacent segments is greater than 45 degrees const bool is_sharp = cos_dir < 0.7071068f; float displacement = 0.0f; if (cos_dir > -0.9998477f) { // if the angle between adjacent segments is smaller than 179 degrees Vec3f med_dir = (prev_dir + next_dir).normalized(); displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f))); } const float sq_prev_length = (curr - prev).squaredNorm(); const float sq_next_length = (next - curr).squaredNorm(); const float sq_displacement = sqr(displacement); const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length&& sq_displacement < sq_next_length; bool is_internal_point = interpolation_points_num > k; if (can_displace) { // displacement to apply to the vertices to match Vec3f displacement_vec = displacement * prev_dir; // matches inner corner vertices if (is_right_turn) match_right_vertices_with_internal_point(prev_sub_path, next_sub_path, curr_s_id, is_internal_point, k, vertex_size_floats, -displacement_vec); else match_left_vertices_with_internal_point(prev_sub_path, next_sub_path, curr_s_id, is_internal_point, k, vertex_size_floats, -displacement_vec); if (!is_sharp) { //BBS: matches outer corner vertices if (is_right_turn) match_left_vertices_with_internal_point(prev_sub_path, next_sub_path, curr_s_id, is_internal_point, k, vertex_size_floats, displacement_vec); else match_right_vertices_with_internal_point(prev_sub_path, next_sub_path, curr_s_id, is_internal_point, k, vertex_size_floats, displacement_vec); } } } } } }; #if ENABLE_GCODE_VIEWER_STATISTICS auto load_vertices_time = std::chrono::high_resolution_clock::now(); m_statistics.load_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS // smooth toolpaths corners for TBuffers using triangles for (size_t i = 0; i < m_buffers.size(); ++i) { const TBuffer& t_buffer = m_buffers[i]; if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { smooth_triangle_toolpaths_corners(t_buffer, vertices[i]); } } // dismiss, no more needed std::vector().swap(biased_seams_ids); for (MultiVertexBuffer& v_multibuffer : vertices) { for (VertexBuffer& v_buffer : v_multibuffer) { v_buffer.shrink_to_fit(); } } // move the wipe toolpaths half height up to render them on proper position MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; for (VertexBuffer& v_buffer : wipe_vertices) { for (size_t i = 2; i < v_buffer.size(); i += 3) { v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height; } } // send vertices data to gpu, where needed for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& t_buffer = m_buffers[i]; if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { const InstanceBuffer& inst_buffer = instances[i]; if (!inst_buffer.empty()) { t_buffer.model.instances.buffer = inst_buffer; t_buffer.model.instances.s_ids = instances_ids[i]; t_buffer.model.instances.offsets = instances_offsets[i]; } } else { if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { const InstanceBuffer& inst_buffer = instances[i]; if (!inst_buffer.empty()) { t_buffer.model.instances.buffer = inst_buffer; t_buffer.model.instances.s_ids = instances_ids[i]; t_buffer.model.instances.offsets = instances_offsets[i]; } } const MultiVertexBuffer& v_multibuffer = vertices[i]; for (const VertexBuffer& v_buffer : v_multibuffer) { const size_t size_elements = v_buffer.size(); const size_t size_bytes = size_elements * sizeof(float); const size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats(); t_buffer.vertices.count += vertices_count; #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.total_vertices_gpu_size += static_cast(size_bytes); m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(size_bytes)); ++m_statistics.vbuffers_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS GLuint id = 0; glsafe(::glGenBuffers(1, &id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); t_buffer.vertices.vbos.push_back(static_cast(id)); t_buffer.vertices.sizes.push_back(size_bytes); } } } #if ENABLE_GCODE_VIEWER_STATISTICS auto smooth_vertices_time = std::chrono::high_resolution_clock::now(); m_statistics.smooth_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - load_vertices_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices); // dismiss vertices data, no more needed std::vector().swap(vertices); std::vector().swap(instances); std::vector().swap(instances_ids); // toolpaths data -> extract indices from result // paths may have been filled while extracting vertices, // so reset them, they will be filled again while extracting indices for (TBuffer& buffer : m_buffers) { buffer.paths.clear(); } // variable used to keep track of the current vertex buffers index and size using CurrVertexBuffer = std::pair; std::vector curr_vertex_buffers(m_buffers.size(), { 0, 0 }); // variable used to keep track of the vertex buffers ids using VboIndexList = std::vector; std::vector vbo_indices(m_buffers.size()); seams_count = 0; for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; if (curr.type == EMoveType::Seam) ++seams_count; size_t move_id = i - seams_count; // skip first vertex if (i == 0) continue; const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessorResult::MoveVertex* next = nullptr; if (i < m_moves_count - 1) next = &gcode_result.moves[i + 1]; ++progress_count; if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), _L("Generating geometry index data") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); progress_dialog->Fit(); progress_count = 0; } const unsigned char id = buffer_id(curr.type); TBuffer& t_buffer = m_buffers[id]; MultiIndexBuffer& i_multibuffer = indices[id]; CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id]; VboIndexList& vbo_index_list = vbo_indices[id]; // ensure there is at least one index buffer if (i_multibuffer.empty()) { i_multibuffer.push_back(IndexBuffer()); if (!t_buffer.vertices.vbos.empty()) vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); } // if adding the indices for the current segment exceeds the threshold size of the current index buffer // create another index buffer // BBS: get the point number and then judge whether the remaining buffer is enough size_t points_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() + 1 : 1; size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : points_num * t_buffer.max_indices_per_segment_size_bytes(); if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) { i_multibuffer.push_back(IndexBuffer()); vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point && t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { Path& last_path = t_buffer.paths.back(); last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); } } // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer // create another index buffer // BBS: support multi points in one MoveVertice, should multiply point number size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : points_num * t_buffer.max_vertices_per_segment_size_bytes(); if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { i_multibuffer.push_back(IndexBuffer()); ++curr_vertex_buffer.first; curr_vertex_buffer.second = 0; vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point && t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { Path& last_path = t_buffer.paths.back(); last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); } } IndexBuffer& i_buffer = i_multibuffer.back(); switch (t_buffer.render_primitive_type) { case TBuffer::ERenderPrimitiveType::Point: { add_indices_as_point(curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); break; } case TBuffer::ERenderPrimitiveType::Line: { add_indices_as_line(prev, curr, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); break; } case TBuffer::ERenderPrimitiveType::Triangle: { add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); break; } case TBuffer::ERenderPrimitiveType::BatchedModel: { add_indices_as_model_batch(t_buffer.model.data, i_buffer, curr_vertex_buffer.second); curr_vertex_buffer.second += t_buffer.model.data.vertices_count(); break; } default: { break; } } } for (MultiIndexBuffer& i_multibuffer : indices) { for (IndexBuffer& i_buffer : i_multibuffer) { i_buffer.shrink_to_fit(); } } // toolpaths data -> send indices data to gpu for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& t_buffer = m_buffers[i]; if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel) { const MultiIndexBuffer& i_multibuffer = indices[i]; for (const IndexBuffer& i_buffer : i_multibuffer) { const size_t size_elements = i_buffer.size(); const size_t size_bytes = size_elements * sizeof(IBufferType); // stores index buffer informations into TBuffer t_buffer.indices.push_back(IBuffer()); IBuffer& ibuf = t_buffer.indices.back(); ibuf.count = size_elements; ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1]; #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.total_indices_gpu_size += static_cast(size_bytes); m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(size_bytes)); ++m_statistics.ibuffers_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS glsafe(::glGenBuffers(1, &ibuf.ibo)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo)); glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } } } if (progress_dialog != nullptr) { progress_dialog->Update(100, ""); progress_dialog->Fit(); } #if ENABLE_GCODE_VIEWER_STATISTICS for (const TBuffer& buffer : m_buffers) { m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); } auto update_segments_count = [&](EMoveType type, int64_t& count) { unsigned int id = buffer_id(type); const MultiIndexBuffer& buffers = indices[id]; int64_t indices_count = 0; for (const IndexBuffer& buffer : buffers) { indices_count += buffer.size(); } const TBuffer& t_buffer = m_buffers[id]; if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) indices_count -= static_cast(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles count += indices_count / t_buffer.indices_per_segment(); }; update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count); update_segments_count(EMoveType::Wipe, m_statistics.wipe_segments_count); update_segments_count(EMoveType::Extrude, m_statistics.extrude_segments_count); m_statistics.load_indices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - smooth_vertices_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS log_memory_usage("Loaded G-code generated indices buffers ", vertices, indices); // dismiss indices data, no more needed std::vector().swap(indices); // layers zs / roles / extruder ids -> extract from result size_t last_travel_s_id = 0; seams_count = 0; for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; if (move.type == EMoveType::Seam) ++seams_count; size_t move_id = i - seams_count; if (move.type == EMoveType::Extrude) { // layers zs const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); const double z = static_cast(move.position.z()); if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) m_layers.append(z, { last_travel_s_id, move_id }); else m_layers.get_endpoints().back().last = move_id; // extruder ids m_extruder_ids.emplace_back(move.extruder_id); // roles if (i > 0) m_roles.emplace_back(move.extrusion_role); } else if (move.type == EMoveType::Travel) { if (move_id - last_travel_s_id > 1 && !m_layers.empty()) m_layers.get_endpoints().back().last = move_id; last_travel_s_id = move_id; } } // roles -> remove duplicates sort_remove_duplicates(m_roles); m_roles.shrink_to_fit(); // extruder ids -> remove duplicates sort_remove_duplicates(m_extruder_ids); m_extruder_ids.shrink_to_fit(); std::vector plater_extruder; for (auto mid : m_extruder_ids){ int eid = mid; plater_extruder.push_back(++eid); } m_plater_extruder = plater_extruder; // replace layers for spiral vase mode if (!gcode_result.spiral_vase_layers.empty()) { m_layers.reset(); for (const auto& layer : gcode_result.spiral_vase_layers) { m_layers.append(layer.first, { layer.second.first, layer.second.second }); } } // set layers z range if (!m_layers.empty()) m_layers_z_range = { 0, static_cast(m_layers.size() - 1) }; // change color of paths whose layer contains option points if (!options_zs.empty()) { TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; for (Path& path : extrude_buffer.paths) { const float z = path.sub_paths.front().first.position.z(); if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) path.cp_color_id = 255 - path.cp_color_id; } } #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS if (progress_dialog != nullptr) progress_dialog->Destroy(); } //BBS: always load shell when preview void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing; if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) { //BBS: update force previewing logic if (force_previewing) m_shells.previewing = force_previewing; //already loaded BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": already loaded, print=%1% print_id=%2%, print_modify_count=%3%, force_previewing %4%")%(&print) %m_shells.print_id %m_shells.print_modify_count %force_previewing; return; } //reset shell firstly reset_shell(); //BBS: move behind of reset_shell, to clear previous shell for empty plate if (print.objects().empty()) { // no shells, return return; } // adds objects' volumes // BBS: fix the issue that object_idx is not assigned as index of Model.objects array int object_count = 0; const ModelObjectPtrs& model_objs = wxGetApp().model().objects; for (const PrintObject* obj : print.objects()) { const ModelObject* model_obj = obj->model_object(); int object_idx = -1; for (int idx = 0; idx < model_objs.size(); idx++) { if (model_objs[idx]->id() == model_obj->id()) { object_idx = idx; break; } } // BBS: object may be deleted when this method is called when deleting an object if (object_idx == -1) continue; std::vector instance_ids(model_obj->instances.size()); //BBS: only add the printable instance int instance_index = 0; for (int i = 0; i < (int)model_obj->instances.size(); ++i) { //BBS: only add the printable instance if (model_obj->instances[i]->is_printable()) instance_ids[instance_index++] = i; } instance_ids.resize(instance_index); size_t current_volumes_count = m_shells.volumes.volumes.size(); m_shells.volumes.load_object(model_obj, object_idx, instance_ids, "object", initialized); // adjust shells' z if raft is present const SlicingParameters& slicing_parameters = obj->slicing_parameters(); if (slicing_parameters.object_print_z_min != 0.0) { const Vec3d z_offset = slicing_parameters.object_print_z_min * Vec3d::UnitZ(); for (size_t i = current_volumes_count; i < m_shells.volumes.volumes.size(); ++i) { GLVolume* v = m_shells.volumes.volumes[i]; v->set_volume_offset(v->get_volume_offset() + z_offset); } } object_count++; } if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { // BBS: adds wipe tower's volume std::vector print_extruders = print.extruders(true); int extruders_count = print_extruders.size(); const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); const PrintConfig& config = print.config(); if (config.enable_prime_tower && (print.enable_timelapse_print() || (extruders_count > 1 && (config.print_sequence == PrintSequence::ByLayer)))) { const float depth = print.wipe_tower_data(extruders_count).depth; const float brim_width = print.wipe_tower_data(extruders_count).brim_width; int plate_idx = print.get_plate_index(); Vec3d plate_origin = print.get_plate_origin(); double wipe_tower_x = config.wipe_tower_x.get_at(plate_idx) + plate_origin(0); double wipe_tower_y = config.wipe_tower_y.get_at(plate_idx) + plate_origin(1); m_shells.volumes.load_wipe_tower_preview(1000, wipe_tower_x, wipe_tower_y, config.prime_tower_width, depth, max_z, config.wipe_tower_rotation_angle, !print.is_step_done(psWipeTower), brim_width, initialized); } } // remove modifiers while (true) { GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); if (it != m_shells.volumes.volumes.end()) { m_shells.volumes.release_volume(*it); delete (*it); m_shells.volumes.volumes.erase(it); } else break; } for (GLVolume* volume : m_shells.volumes.volumes) { volume->zoom_to_volumes = false; volume->color[3] = 0.5f; volume->force_native_color = true; volume->set_render_color(); //BBS: add shell bounding box logic m_shell_bounding_box.merge(volume->transformed_bounding_box()); } //BBS: always load shell when preview m_shells.print_id = print.id().id; m_shells.print_modify_count = print.get_modified_count(); m_shells.previewing = true; BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": shell loaded, id change to %1%, modify_count %2%, object count %3%, glvolume count %4%") % m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size(); } void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const { #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": enter, m_buffers size %1%!")%m_buffers.size(); auto extrusion_color = [this](const Path& path) { Color color; switch (m_view_type) { case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } case EViewType::LayerTime: { color = m_extrusions.ranges.layer_duration.get_color_at(path.layer_time, Extrusions::Range::EType::Logarithmic); break; } case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tools.m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { if (path.cp_color_id >= static_cast(m_tools.m_tool_colors.size())) color = { 0.5f, 0.5f, 0.5f, 1.0f }; else { color = m_tools.m_tool_colors[path.cp_color_id]; color = adjust_color_for_rendering(color); } break; } case EViewType::FilamentId: { float id = float(path.extruder_id)/256; float role = float(path.role) / 256; color = {id, role, id, 1.0f}; break; } default: { color = { 1.0f, 1.0f, 1.0f, 1.0f }; break; } } return color; }; auto travel_color = [](const Path& path) { return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : Travel_Colors[0] /* Move */); }; auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { auto in_layers_range = [this, min_id, max_id](size_t id) { return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last; }; return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id); }; //BBS auto is_extruder_in_layer_range = [this](const Path& path, size_t extruder_id) { return path.extruder_id == extruder_id; }; auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) { const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; if (path_id >= buffer.paths.size()) return false; Path path = buffer.paths[path_id]; size_t first = path_id; size_t last = path_id; // check adjacent paths while (first > 0 && path.sub_paths.front().first.position.isApprox(buffer.paths[first - 1].sub_paths.back().last.position)) { --first; path.sub_paths.front().first = buffer.paths[first].sub_paths.front().first; } while (last < buffer.paths.size() - 1 && path.sub_paths.back().last.position.isApprox(buffer.paths[last + 1].sub_paths.front().first.position)) { ++last; path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last; } const size_t min_s_id = m_layers.get_endpoints_at(min_id).first; const size_t max_s_id = m_layers.get_endpoints_at(max_id).last; return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) || (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id); }; #if ENABLE_GCODE_VIEWER_STATISTICS Statistics* statistics = const_cast(&m_statistics); statistics->render_paths_size = 0; statistics->models_instances_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS const bool top_layer_only = true; //BBS SequentialView::Endpoints global_endpoints = { m_sequential_view.gcode_ids.size() , 0 }; SequentialView::Endpoints top_layer_endpoints = global_endpoints; SequentialView* sequential_view = const_cast(&m_sequential_view); if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; //BBS if (!keep_sequential_current_last) sequential_view->current.last = m_sequential_view.gcode_ids.size(); // first pass: collect visible paths and update sequential view data std::vector> paths; for (size_t b = 0; b < m_buffers.size(); ++b) { TBuffer& buffer = const_cast(m_buffers[b]); // reset render paths buffer.render_paths.clear(); if (!buffer.visible) continue; if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { for (size_t id : buffer.model.instances.s_ids) { if (id < m_layers.get_endpoints_at(m_layers_z_range[0]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id) continue; global_endpoints.first = std::min(global_endpoints.first, id); global_endpoints.last = std::max(global_endpoints.last, id); if (top_layer_only) { if (id < m_layers.get_endpoints_at(m_layers_z_range[1]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id) continue; top_layer_endpoints.first = std::min(top_layer_endpoints.first, id); top_layer_endpoints.last = std::max(top_layer_endpoints.last, id); } } } else { for (size_t i = 0; i < buffer.paths.size(); ++i) { const Path& path = buffer.paths[i]; if (path.type == EMoveType::Travel) { if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) continue; } else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) continue; if (path.type == EMoveType::Extrude && !is_visible(path)) continue; if (m_view_type == EViewType::ColorPrint && !m_tools.m_tool_visibles[path.extruder_id]) continue; // store valid path for (size_t j = 0; j < path.sub_paths.size(); ++j) { paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); } global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id); if (top_layer_only) { if (path.type == EMoveType::Travel) { if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); } } else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); } } } } } // update current sequential position sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; if (global_endpoints.last == 0) { sequential_view->current.last = global_endpoints.last; } else { sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; } // get the world position from the vertex buffer bool found = false; for (const TBuffer& buffer : m_buffers) { if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) { if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) { size_t offset = i * buffer.model.instances.instance_size_floats(); sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0]; sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1]; sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2]; sequential_view->current_offset = buffer.model.instances.offsets[i]; found = true; break; } } } else { // searches the path containing the current position for (const Path& path : buffer.paths) { if (path.contains(m_sequential_view.current.last)) { const int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last); if (sub_path_id != -1) { const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; unsigned int offset = static_cast(m_sequential_view.current.last - sub_path.first.s_id); if (offset > 0) { if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) { for (size_t i = sub_path.first.s_id + 1; i < m_sequential_view.current.last + 1; i++) { size_t move_id = m_ssid_to_moveid_map[i]; const GCodeProcessorResult::MoveVertex& curr = m_gcode_result->moves[move_id]; if (curr.is_arc_move()) { offset += curr.interpolation_points.size(); } } offset = 2 * offset - 1; } else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { unsigned int indices_count = buffer.indices_per_segment(); // BBS: modify to support moves which has internal point for (size_t i = sub_path.first.s_id + 1; i < m_sequential_view.current.last + 1; i++) { size_t move_id = m_ssid_to_moveid_map[i]; const GCodeProcessorResult::MoveVertex& curr = m_gcode_result->moves[move_id]; if (curr.is_arc_move()) { offset += curr.interpolation_points.size(); } } offset = indices_count * (offset - 1) + (indices_count - 2); if (sub_path_id == 0) offset += 6; // add 2 triangles for starting cap } } offset += static_cast(sub_path.first.i_id); // gets the vertex index from the index buffer on gpu const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id]; unsigned int index = 0; glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&index))); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); // gets the position from the vertices buffer on gpu glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); sequential_view->current_offset = Vec3f::Zero(); found = true; break; } } } } if (found) break; } // second pass: filter paths by sequential data and collect them by color RenderPath* render_path = nullptr; for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); const Path& path = buffer.paths[path_id]; const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) continue; Color color; switch (path.type) { case EMoveType::Tool_change: case EMoveType::Color_change: case EMoveType::Pause_Print: case EMoveType::Custom_GCode: case EMoveType::Retract: case EMoveType::Unretract: case EMoveType::Seam: { color = option_color(path.type); break; } case EMoveType::Extrude: { if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) color = extrusion_color(path); else color = Neutral_Color; break; } case EMoveType::Travel: { if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool) ? extrusion_color(path) : travel_color(path); else color = Neutral_Color; break; } case EMoveType::Wipe: { color = Wipe_Color; break; } default: { color = { 0.0f, 0.0f, 0.0f, 1.0f }; break; } } RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) { buffer.render_paths.emplace_back(key); render_path = const_cast(&buffer.render_paths.back()); } unsigned int delta_1st = 0; if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) delta_1st = static_cast(m_sequential_view.current.first - sub_path.first.s_id); unsigned int size_in_indices = 0; switch (buffer.render_primitive_type) { case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = buffer.indices_per_segment(); break; } case TBuffer::ERenderPrimitiveType::Line: case TBuffer::ERenderPrimitiveType::Triangle: { // BBS: modify to support moves which has internal point size_t max_s_id = std::min(m_sequential_view.current.last, sub_path.last.s_id); size_t min_s_id = std::max(m_sequential_view.current.first, sub_path.first.s_id); unsigned int segments_count = max_s_id - min_s_id; for (size_t i = min_s_id + 1; i < max_s_id + 1; i++) { size_t move_id = m_ssid_to_moveid_map[i]; const GCodeProcessorResult::MoveVertex& curr = m_gcode_result->moves[move_id]; if (curr.is_arc_move()) { segments_count += curr.interpolation_points.size(); } } size_in_indices = buffer.indices_per_segment() * segments_count; break; } default: { break; } } if (size_in_indices == 0) continue; if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { if (sub_path_id == 0 && delta_1st == 0) size_in_indices += 6; // add 2 triangles for starting cap if (sub_path_id == path.sub_paths.size() - 1 && path.sub_paths.back().last.s_id <= m_sequential_view.current.last) size_in_indices += 6; // add 2 triangles for ending cap if (delta_1st > 0) size_in_indices -= 6; // remove 2 triangles for corner cap } render_path->sizes.push_back(size_in_indices); if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { delta_1st *= buffer.indices_per_segment(); if (delta_1st > 0) { delta_1st += 6; // skip 2 triangles for corner cap if (sub_path_id == 0) delta_1st += 6; // skip 2 triangles for starting cap } } render_path->offsets.push_back(static_cast((sub_path.first.i_id + delta_1st) * sizeof(IBufferType))); #if 0 // check sizes and offsets against index buffer size on gpu GLint buffer_size; glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indices[render_path->ibuffer_id].ibo)); glsafe(::glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &buffer_size)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); if (render_path->offsets.back() + render_path->sizes.back() * sizeof(IBufferType) > buffer_size) BOOST_LOG_TRIVIAL(error) << "GCodeViewer::refresh_render_paths: Invalid render path data"; #endif } // Removes empty render paths and sort. for (size_t b = 0; b < m_buffers.size(); ++b) { TBuffer* buffer = const_cast(&m_buffers[b]); buffer->render_paths.erase(std::remove_if(buffer->render_paths.begin(), buffer->render_paths.end(), [](const auto &path){ return path.sizes.empty() || path.offsets.empty(); }), buffer->render_paths.end()); } // second pass: for buffers using instanced and batched models, update the instances render ranges for (size_t b = 0; b < m_buffers.size(); ++b) { TBuffer& buffer = const_cast(m_buffers[b]); if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel && buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) continue; buffer.model.instances.render_ranges.reset(); if (!buffer.visible || buffer.model.instances.s_ids.empty()) continue; buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, buffer.model.color }); bool has_second_range = top_layer_only && m_sequential_view.current.last != m_sequential_view.global.last; if (has_second_range) buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, Neutral_Color }); if (m_sequential_view.current.first <= buffer.model.instances.s_ids.back() && buffer.model.instances.s_ids.front() <= m_sequential_view.current.last) { for (size_t id : buffer.model.instances.s_ids) { if (has_second_range) { if (id < m_sequential_view.endpoints.first) { ++buffer.model.instances.render_ranges.ranges.front().offset; if (id <= m_sequential_view.current.first) ++buffer.model.instances.render_ranges.ranges.back().offset; else ++buffer.model.instances.render_ranges.ranges.back().count; } else if (id <= m_sequential_view.current.last) ++buffer.model.instances.render_ranges.ranges.front().count; else break; } else { if (id <= m_sequential_view.current.first) ++buffer.model.instances.render_ranges.ranges.front().offset; else if (id <= m_sequential_view.current.last) ++buffer.model.instances.render_ranges.ranges.front().count; else break; } } } } // set sequential data to their final value sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; sequential_view->global = global_endpoints; // updates sequential range caps std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); (*sequential_range_caps)[0].reset(); (*sequential_range_caps)[1].reset(); if (m_sequential_view.current.first != m_sequential_view.current.last) { for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) continue; const Path& path = buffer.paths[path_id]; const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first) continue; // update cap for first endpoint of current range if (m_sequential_view.current.first > sub_path.first.s_id) { SequentialRangeCap& cap = (*sequential_range_caps)[0]; const IBuffer& i_buffer = buffer.indices[ibuffer_id]; cap.buffer = &buffer; cap.vbo = i_buffer.vbo; // calculate offset into the index buffer unsigned int offset = sub_path.first.i_id; offset += 6; // add 2 triangles for corner cap offset += static_cast(m_sequential_view.current.first - sub_path.first.s_id) * buffer.indices_per_segment(); if (sub_path_id == 0) offset += 6; // add 2 triangles for starting cap // extract indices from index buffer std::array indices{ 0, 0, 0, 0, 0, 0 }; glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 0) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 7) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 1) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 13) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[4]))); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); indices[3] = indices[0]; indices[5] = indices[1]; // send indices to gpu glsafe(::glGenBuffers(1, &cap.ibo)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); // extract color from render path size_t offset_bytes = offset * sizeof(IBufferType); for (const RenderPath& render_path : buffer.render_paths) { if (render_path.ibuffer_id == ibuffer_id) { for (size_t j = 0; j < render_path.offsets.size(); ++j) { if (render_path.contains(offset_bytes)) { cap.color = render_path.color; break; } } } } } // update cap for last endpoint of current range if (m_sequential_view.current.last < sub_path.last.s_id) { SequentialRangeCap& cap = (*sequential_range_caps)[1]; const IBuffer& i_buffer = buffer.indices[ibuffer_id]; cap.buffer = &buffer; cap.vbo = i_buffer.vbo; // calculate offset into the index buffer unsigned int offset = sub_path.first.i_id; offset += 6; // add 2 triangles for corner cap offset += static_cast(m_sequential_view.current.last - 1 - sub_path.first.s_id) * buffer.indices_per_segment(); if (sub_path_id == 0) offset += 6; // add 2 triangles for starting cap // extract indices from index buffer std::array indices{ 0, 0, 0, 0, 0, 0 }; glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 2) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 4) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 10) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 16) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[5]))); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); indices[3] = indices[0]; indices[4] = indices[2]; // send indices to gpu glsafe(::glGenBuffers(1, &cap.ibo)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); // extract color from render path size_t offset_bytes = offset * sizeof(IBufferType); for (const RenderPath& render_path : buffer.render_paths) { if (render_path.ibuffer_id == ibuffer_id) { for (size_t j = 0; j < render_path.offsets.size(); ++j) { if (render_path.contains(offset_bytes)) { cap.color = render_path.color; break; } } } } } if ((*sequential_range_caps)[0].is_renderable() && (*sequential_range_caps)[1].is_renderable()) break; } } //BBS enable_moves_slider(!paths.empty()); #if ENABLE_GCODE_VIEWER_STATISTICS for (const TBuffer& buffer : m_buffers) { statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); for (const RenderPath& path : buffer.render_paths) { statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); } statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.buffer, float); statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.s_ids, size_t); statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.render_ranges.ranges, InstanceVBuffer::Ranges::Range); } statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } void GCodeViewer::render_toolpaths() { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS float point_size = 20.0f; #else float point_size = 0.8f; #endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS std::array light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f }; const Camera& camera = wxGetApp().plater()->get_camera(); double zoom = camera.get_zoom(); const std::array& viewport = camera.get_viewport(); float near_plane_height = camera.get_type() == Camera::EType::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : static_cast(viewport[3]) * 0.0005; auto shader_init_as_points = [zoom, point_size, near_plane_height](GLShaderProgram& shader) { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS shader.set_uniform("use_fixed_screen_size", 1); #else shader.set_uniform("use_fixed_screen_size", 0); #endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS shader.set_uniform("zoom", zoom); shader.set_uniform("percent_outline_radius", 0.0f); shader.set_uniform("percent_center_radius", 0.33f); shader.set_uniform("point_size", point_size); shader.set_uniform("near_plane_height", near_plane_height); }; auto render_as_points = [ #if ENABLE_GCODE_VIEWER_STATISTICS this #endif // ENABLE_GCODE_VIEWER_STATISTICS ](std::vector::reverse_iterator it_path, std::vector::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) { glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); glsafe(::glEnable(GL_POINT_SPRITE)); for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { const RenderPath& path = *it; // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(! path.sizes.empty()); assert(! path.offsets.empty()); glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } glsafe(::glDisable(GL_POINT_SPRITE)); glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; auto shader_init_as_lines = [light_intensity](GLShaderProgram &shader) { shader.set_uniform("light_intensity", light_intensity); }; auto render_as_lines = [ #if ENABLE_GCODE_VIEWER_STATISTICS this #endif // ENABLE_GCODE_VIEWER_STATISTICS ](std::vector::reverse_iterator it_path, std::vector::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) { for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { const RenderPath& path = *it; // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(! path.sizes.empty()); assert(! path.offsets.empty()); glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_lines_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } }; auto render_as_triangles = [ #if ENABLE_GCODE_VIEWER_STATISTICS this #endif // ENABLE_GCODE_VIEWER_STATISTICS ](std::vector::reverse_iterator it_path, std::vector::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) { for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { const RenderPath& path = *it; // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(! path.sizes.empty()); assert(! path.offsets.empty()); glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } }; auto render_as_instanced_model = [ #if ENABLE_GCODE_VIEWER_STATISTICS this #endif // ENABLE_GCODE_VIEWER_STATISTICS ](TBuffer& buffer, GLShaderProgram & shader) { for (auto& range : buffer.model.instances.render_ranges.ranges) { if (range.vbo == 0 && range.count > 0) { glsafe(::glGenBuffers(1, &range.vbo)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo)); glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } if (range.vbo > 0) { buffer.model.model.set_color(-1, range.color); buffer.model.model.render_instanced(range.vbo, range.count); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_instanced_models_calls_count; m_statistics.total_instances_gpu_size += static_cast(range.count * buffer.model.instances.instance_size_bytes()); #endif // ENABLE_GCODE_VIEWER_STATISTICS } } }; #if ENABLE_GCODE_VIEWER_STATISTICS auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { #else auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { #endif // ENABLE_GCODE_VIEWER_STATISTICS struct Range { unsigned int first; unsigned int last; bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } }; Range buffer_range = { 0, 0 }; size_t indices_per_instance = buffer.model.data.indices_count(); for (size_t j = 0; j < buffer.indices.size(); ++j) { const IBuffer& i_buffer = buffer.indices[j]; buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); bool has_normals = buffer.vertices.normal_size_floats() > 0; if (has_normals) { glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); for (auto& range : buffer.model.instances.render_ranges.ranges) { Range range_range = { range.offset, range.offset + range.count }; if (range_range.intersects(buffer_range)) { shader.set_uniform("uniform_color", range.color); unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; if (count > 0) { glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_batched_models_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); if (has_normals) glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); buffer_range.first = buffer_range.last; } }; auto line_width = [](double zoom) { return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); }; unsigned char begin_id = buffer_id(EMoveType::Retract); unsigned char end_id = buffer_id(EMoveType::Count); //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":begin_id %1%, end_id %2% ")%(int)begin_id %(int)end_id; for (unsigned char i = begin_id; i < end_id; ++i) { TBuffer& buffer = m_buffers[i]; if (!buffer.visible || !buffer.has_data()) continue; GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); if (shader != nullptr) { shader->start_using(); if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { shader->set_uniform("emission_factor", 0.25f); render_as_instanced_model(buffer, *shader); shader->set_uniform("emission_factor", 0.0f); } else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { shader->set_uniform("emission_factor", 0.25f); render_as_batched_model(buffer, *shader); shader->set_uniform("emission_factor", 0.0f); } else { switch (buffer.render_primitive_type) { case TBuffer::ERenderPrimitiveType::Point: shader_init_as_points(*shader); break; case TBuffer::ERenderPrimitiveType::Line: shader_init_as_lines(*shader); break; default: break; } int uniform_color = shader->get_uniform_location("uniform_color"); auto it_path = buffer.render_paths.rbegin(); //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":buffer indices size %1%, render_path size %2% ")%buffer.indices.size() %buffer.render_paths.size(); unsigned int indices_count = static_cast(buffer.indices.size()); for (unsigned int index = 0; index < indices_count; ++index) { unsigned int ibuffer_id = indices_count - index - 1; const IBuffer& i_buffer = buffer.indices[ibuffer_id]; // Skip all paths with ibuffer_id < ibuffer_id. for (; it_path != buffer.render_paths.rend() && it_path->ibuffer_id > ibuffer_id; ++ it_path) ; if (it_path == buffer.render_paths.rend() || it_path->ibuffer_id < ibuffer_id) // Not found. This shall not happen. continue; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); bool has_normals = buffer.vertices.normal_size_floats() > 0; if (has_normals) { glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors. switch (buffer.render_primitive_type) { case TBuffer::ERenderPrimitiveType::Point: { render_as_points(it_path, buffer.render_paths.rend(), *shader, uniform_color); break; } case TBuffer::ERenderPrimitiveType::Line: { glsafe(::glLineWidth(static_cast(line_width(zoom)))); render_as_lines(it_path, buffer.render_paths.rend(), *shader, uniform_color); break; } case TBuffer::ERenderPrimitiveType::Triangle: { render_as_triangles(it_path, buffer.render_paths.rend(), *shader, uniform_color); break; } default: { break; } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); if (has_normals) glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } } shader->stop_using(); } } #if ENABLE_GCODE_VIEWER_STATISTICS auto render_sequential_range_cap = [this] #else auto render_sequential_range_cap = [] #endif // ENABLE_GCODE_VIEWER_STATISTICS (const SequentialRangeCap& cap) { GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); if (shader != nullptr) { shader->start_using(); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_bytes())); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); bool has_normals = cap.buffer->vertices.normal_size_floats() > 0; if (has_normals) { glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_bytes())); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } shader->set_uniform("uniform_color", cap.color); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS if (has_normals) glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); shader->stop_using(); } }; for (unsigned int i = 0; i < 2; ++i) { if (m_sequential_range_caps[i].is_renderable()) render_sequential_range_cap(m_sequential_range_caps[i]); } } void GCodeViewer::render_shells() { //BBS: add shell previewing logic if ((!m_shells.previewing && !m_shells.visible) || m_shells.volumes.empty()) //if (!m_shells.visible || m_shells.volumes.empty()) return; GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; // when the background processing is enabled, it may happen that the shells data have been loaded // before opengl has been initialized for the preview canvas. // when this happens, the volumes' data have not been sent to gpu yet. for (GLVolume* v : m_shells.volumes.volumes) { if (!v->indexed_vertex_array->has_VBOs()) v->finalize_geometry(true); } glsafe(::glEnable(GL_DEPTH_TEST)); // glsafe(::glDepthMask(GL_FALSE)); shader->start_using(); //BBS: reopen cul faces m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); shader->stop_using(); // glsafe(::glDepthMask(GL_TRUE)); } //BBS void GCodeViewer::render_all_plates_stats(const std::vector& gcode_result_list, bool show /*= true*/) const { if (!show) return; for (auto gcode_result : gcode_result_list) { if (gcode_result->moves.size() == 0) return; } ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0, 10.0 * m_scale)); ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f, 1.0f, 1.0f, 0.6f)); ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(340.f * m_scale * imgui.scaled(1.0f / 15.0f), 0)); ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), 0, ImVec2(0.5f, 0.5f)); ImGui::Begin(_L("Statistics of All Plates").c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); ImDrawList* draw_list = ImGui::GetWindowDrawList(); std::vector filament_diameters = gcode_result_list.front()->filament_diameters; std::vector filament_densities = gcode_result_list.front()->filament_densities; std::vector filament_colors = decode_colors(wxGetApp().plater()->get_extruder_colors_from_plater_config(gcode_result_list.back())); for (int i = 0; i < filament_colors.size(); i++) { filament_colors[i] = adjust_color_for_rendering(filament_colors[i]); } bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; float window_padding = 4.0f * m_scale; const float icon_size = ImGui::GetTextLineHeight() * 0.7; std::map offsets; std::map model_volume_of_extruders_all_plates; // map std::map flushed_volume_of_extruders_all_plates; // map std::map wipe_tower_volume_of_extruders_all_plates; // map std::map support_volume_of_extruders_all_plates; // map std::map plate_time; // map std::vector model_used_filaments_m_all_plates; std::vector model_used_filaments_g_all_plates; std::vector flushed_filaments_m_all_plates; std::vector flushed_filaments_g_all_plates; std::vector wipe_tower_used_filaments_m_all_plates; std::vector wipe_tower_used_filaments_g_all_plates; std::vector support_used_filaments_m_all_plates; std::vector support_used_filaments_g_all_plates; float total_time_all_plates = 0.0f; float total_cost_all_plates = 0.0f; double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1.0; struct ColumnData { enum { Model = 1, Flushed = 2, WipeTower = 4, Support = 1 << 3, }; }; int displayed_columns = 0; auto max_width = [](const std::vector& items, const std::string& title, float extra_size = 0.0f) { float ret = ImGui::CalcTextSize(title.c_str()).x; for (const std::string& item : items) { ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x); } return ret; }; auto calculate_offsets = [max_width, window_padding](const std::vector>>& title_columns, float extra_size = 0.0f) { const ImGuiStyle& style = ImGui::GetStyle(); std::vector offsets; offsets.push_back(max_width(title_columns[0].second, title_columns[0].first, extra_size) + 3.0f * style.ItemSpacing.x + style.WindowPadding.x); for (size_t i = 1; i < title_columns.size() - 1; i++) offsets.push_back(offsets.back() + max_width(title_columns[i].second, title_columns[i].first) + style.ItemSpacing.x); if (title_columns.back().first == _u8L("Display")) offsets.back() = ImGui::GetWindowWidth() - ImGui::CalcTextSize(_u8L("Display").c_str()).x - ImGui::GetFrameHeight() / 2 - 2 * window_padding; float average_col_width = ImGui::GetWindowWidth() / static_cast(title_columns.size()); std::vector ret; ret.push_back(0); for (size_t i = 1; i < title_columns.size(); i++) { ret.push_back(std::max(offsets[i - 1], i * average_col_width)); } return ret; }; auto append_item = [icon_size, &imgui, imperial_units, &window_padding, &draw_list, this](bool draw_icon, const Color& color, const std::vector>& columns_offsets) { // render icon ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y); if (draw_icon) draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0 * m_scale, 6.0 * m_scale)); // render selectable ImGui::Dummy({ 0.0, 0.0 }); ImGui::SameLine(); // render column item { float dummy_size = draw_icon ? ImGui::GetStyle().ItemSpacing.x + icon_size : window_padding * 3; ImGui::SameLine(dummy_size); imgui.text(columns_offsets[0].first); for (auto i = 1; i < columns_offsets.size(); i++) { ImGui::SameLine(columns_offsets[i].second); imgui.text(columns_offsets[i].first); } } ImGui::PopStyleVar(1); }; auto append_headers = [&imgui](const std::vector>& title_offsets) { for (size_t i = 0; i < title_offsets.size(); i++) { ImGui::SameLine(title_offsets[i].second); imgui.bold_text(title_offsets[i].first); } ImGui::Separator(); }; auto get_used_filament_from_volume = [this, imperial_units, &filament_diameters, &filament_densities](double volume, int extruder_id) { double koef = imperial_units ? 1.0 / GizmoObjectManipulation::in_to_mm : 0.001; std::pair ret = { koef * volume / (PI * sqr(0.5 * filament_diameters[extruder_id])), volume * filament_densities[extruder_id] * 0.001 }; return ret; }; ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); // title and item data { PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list(); for (auto plate : plate_list.get_nonempty_plate_list()) { auto plate_print_statistics = plate->get_slice_result()->print_statistics; auto plate_extruders = plate->get_extruders(true); for (size_t extruder_id : plate_extruders) { extruder_id -= 1; if (plate_print_statistics.model_volumes_per_extruder.find(extruder_id) == plate_print_statistics.model_volumes_per_extruder.end()) model_volume_of_extruders_all_plates[extruder_id] += 0; else { double model_volume = plate_print_statistics.model_volumes_per_extruder.at(extruder_id); model_volume_of_extruders_all_plates[extruder_id] += model_volume; } if (plate_print_statistics.flush_per_filament.find(extruder_id) == plate_print_statistics.flush_per_filament.end()) flushed_volume_of_extruders_all_plates[extruder_id] += 0; else { double flushed_volume = plate_print_statistics.flush_per_filament.at(extruder_id); flushed_volume_of_extruders_all_plates[extruder_id] += flushed_volume; } if (plate_print_statistics.wipe_tower_volumes_per_extruder.find(extruder_id) == plate_print_statistics.wipe_tower_volumes_per_extruder.end()) wipe_tower_volume_of_extruders_all_plates[extruder_id] += 0; else { double wipe_tower_volume = plate_print_statistics.wipe_tower_volumes_per_extruder.at(extruder_id); wipe_tower_volume_of_extruders_all_plates[extruder_id] += wipe_tower_volume; } if (plate_print_statistics.support_volumes_per_extruder.find(extruder_id) == plate_print_statistics.support_volumes_per_extruder.end()) support_volume_of_extruders_all_plates[extruder_id] += 0; else { double support_volume = plate_print_statistics.support_volumes_per_extruder.at(extruder_id); support_volume_of_extruders_all_plates[extruder_id] += support_volume; } } const PrintEstimatedStatistics::Mode& plate_time_mode = plate_print_statistics.modes[static_cast(m_time_estimate_mode)]; plate_time.insert_or_assign(plate->get_index(), plate_time_mode.time); total_time_all_plates += plate_time_mode.time; Print* print; plate->get_print((PrintBase**)&print, nullptr, nullptr); total_cost_all_plates += print->print_statistics().total_cost; } for (auto it = model_volume_of_extruders_all_plates.begin(); it != model_volume_of_extruders_all_plates.end(); it++) { auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(it->second, it->first); if (model_used_filament_m != 0.0 || model_used_filament_g != 0.0) displayed_columns |= ColumnData::Model; model_used_filaments_m_all_plates.push_back(model_used_filament_m); model_used_filaments_g_all_plates.push_back(model_used_filament_g); } for (auto it = flushed_volume_of_extruders_all_plates.begin(); it != flushed_volume_of_extruders_all_plates.end(); it++) { auto [flushed_filament_m, flushed_filament_g] = get_used_filament_from_volume(it->second, it->first); if (flushed_filament_m != 0.0 || flushed_filament_g != 0.0) displayed_columns |= ColumnData::Flushed; flushed_filaments_m_all_plates.push_back(flushed_filament_m); flushed_filaments_g_all_plates.push_back(flushed_filament_g); } for (auto it = wipe_tower_volume_of_extruders_all_plates.begin(); it != wipe_tower_volume_of_extruders_all_plates.end(); it++) { auto [wipe_tower_filament_m, wipe_tower_filament_g] = get_used_filament_from_volume(it->second, it->first); if (wipe_tower_filament_m != 0.0 || wipe_tower_filament_g != 0.0) displayed_columns |= ColumnData::WipeTower; wipe_tower_used_filaments_m_all_plates.push_back(wipe_tower_filament_m); wipe_tower_used_filaments_g_all_plates.push_back(wipe_tower_filament_g); } for (auto it = support_volume_of_extruders_all_plates.begin(); it != support_volume_of_extruders_all_plates.end(); it++) { auto [support_filament_m, support_filament_g] = get_used_filament_from_volume(it->second, it->first); if (support_filament_m != 0.0 || support_filament_g != 0.0) displayed_columns |= ColumnData::Support; support_used_filaments_m_all_plates.push_back(support_filament_m); support_used_filaments_g_all_plates.push_back(support_filament_g); } char buff[64]; double longest_str = 0.0; for (auto i : model_used_filaments_g_all_plates) { if (i > longest_str) longest_str = i; } ::sprintf(buff, "%.2f", longest_str); std::vector>> title_columns; if (displayed_columns & ColumnData::Model) { title_columns.push_back({ _u8L("Filament"), {""} }); title_columns.push_back({ _u8L("Model"), {buff} }); } if (displayed_columns & ColumnData::Support) { title_columns.push_back({ _u8L("Support"), {buff} }); } if (displayed_columns & ColumnData::Flushed) { title_columns.push_back({ _u8L("Flushed"), {buff} }); } if (displayed_columns & ColumnData::WipeTower) { title_columns.push_back({ _u8L("Tower"), {buff} }); } if ((displayed_columns & ~ColumnData::Model) > 0) { title_columns.push_back({ _u8L("Total"), {buff} }); } auto offsets_ = calculate_offsets(title_columns, icon_size); std::vector> title_offsets; for (int i = 0; i < offsets_.size(); i++) { title_offsets.push_back({ title_columns[i].first, offsets_[i] }); offsets[title_columns[i].first] = offsets_[i]; } append_headers(title_offsets); } // item { size_t i = 0; for (auto it = model_volume_of_extruders_all_plates.begin(); it != model_volume_of_extruders_all_plates.end(); it++) { if (i < model_used_filaments_m_all_plates.size() && i < model_used_filaments_g_all_plates.size()) { std::vector> columns_offsets; columns_offsets.push_back({ std::to_string(it->first + 1), offsets[_u8L("Filament")]}); char buf[64]; float column_sum_m = 0.0f; float column_sum_g = 0.0f; if (displayed_columns & ColumnData::Model) { if ((displayed_columns & ~ColumnData::Model) > 0) ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] / unit_conver); else ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] / unit_conver); columns_offsets.push_back({ buf, offsets[_u8L("Model")] }); column_sum_m += model_used_filaments_m_all_plates[i]; column_sum_g += model_used_filaments_g_all_plates[i]; } if (displayed_columns & ColumnData::Support) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m_all_plates[i], support_used_filaments_g_all_plates[i] / unit_conver); columns_offsets.push_back({ buf, offsets[_u8L("Support")] }); column_sum_m += support_used_filaments_m_all_plates[i]; column_sum_g += support_used_filaments_g_all_plates[i]; } if (displayed_columns & ColumnData::Flushed) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m_all_plates[i], flushed_filaments_g_all_plates[i] / unit_conver); columns_offsets.push_back({ buf, offsets[_u8L("Flushed")] }); column_sum_m += flushed_filaments_m_all_plates[i]; column_sum_g += flushed_filaments_g_all_plates[i]; } if (displayed_columns & ColumnData::WipeTower) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m_all_plates[i], wipe_tower_used_filaments_g_all_plates[i] / unit_conver); columns_offsets.push_back({ buf, offsets[_u8L("Tower")] }); column_sum_m += wipe_tower_used_filaments_m_all_plates[i]; column_sum_g += wipe_tower_used_filaments_g_all_plates[i]; } if ((displayed_columns & ~ColumnData::Model) > 0) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver); columns_offsets.push_back({ buf, offsets[_u8L("Total")] }); } append_item(true, filament_colors[it->first], columns_offsets); } i++; } // Sum of all rows char buf[64]; if (model_volume_of_extruders_all_plates.size() > 1) { // Separator ImGuiWindow *window = ImGui::GetCurrentWindow(); const ImRect separator(ImVec2(window->Pos.x + window_padding * 3, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x - window_padding * 3, window->DC.CursorPos.y + 1.0f)); ImGui::ItemSize(ImVec2(0.0f, 0.0f)); const bool item_visible = ImGui::ItemAdd(separator, 0); window->DrawList->AddLine(separator.Min, ImVec2(separator.Max.x, separator.Min.y), ImGui::GetColorU32(ImGuiCol_Separator)); std::vector> columns_offsets; columns_offsets.push_back({_u8L("Total"), offsets[_u8L("Filament")]}); double total_model_used_filament_m = 0; double total_model_used_filament_g = 0; double total_support_used_filament_m = 0; double total_support_used_filament_g = 0; double total_flushed_filament_m = 0; double total_flushed_filament_g = 0; double total_wipe_tower_used_filament_m = 0; double total_wipe_tower_used_filament_g = 0; if (displayed_columns & ColumnData::Model) { std::for_each(model_used_filaments_m_all_plates.begin(), model_used_filaments_m_all_plates.end(), [&total_model_used_filament_m](double value) { total_model_used_filament_m += value; }); std::for_each(model_used_filaments_g_all_plates.begin(), model_used_filaments_g_all_plates.end(), [&total_model_used_filament_g](double value) { total_model_used_filament_g += value; }); if ((displayed_columns & ~ColumnData::Model) > 0) ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); else ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); columns_offsets.push_back({buf, offsets[_u8L("Model")]}); } if (displayed_columns & ColumnData::Support) { std::for_each(model_used_filaments_m_all_plates.begin(), model_used_filaments_m_all_plates.end(), [&total_support_used_filament_m](double value) { total_support_used_filament_m += value; }); std::for_each(model_used_filaments_g_all_plates.begin(), model_used_filaments_g_all_plates.end(), [&total_support_used_filament_g](double value) { total_support_used_filament_g += value; }); ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_support_used_filament_m, total_support_used_filament_g / unit_conver); columns_offsets.push_back({buf, offsets[_u8L("Support")]}); } if (displayed_columns & ColumnData::Flushed) { std::for_each(flushed_filaments_m_all_plates.begin(), flushed_filaments_m_all_plates.end(), [&total_flushed_filament_m](double value) { total_flushed_filament_m += value; }); std::for_each(flushed_filaments_g_all_plates.begin(), flushed_filaments_g_all_plates.end(), [&total_flushed_filament_g](double value) { total_flushed_filament_g += value; }); ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_flushed_filament_m, total_flushed_filament_g / unit_conver); columns_offsets.push_back({buf, offsets[_u8L("Flushed")]}); } if (displayed_columns & ColumnData::WipeTower) { std::for_each(wipe_tower_used_filaments_m_all_plates.begin(), wipe_tower_used_filaments_m_all_plates.end(), [&total_wipe_tower_used_filament_m](double value) { total_wipe_tower_used_filament_m += value; }); std::for_each(wipe_tower_used_filaments_g_all_plates.begin(), wipe_tower_used_filaments_g_all_plates.end(), [&total_wipe_tower_used_filament_g](double value) { total_wipe_tower_used_filament_g += value; }); ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_wipe_tower_used_filament_m, total_wipe_tower_used_filament_g / unit_conver); columns_offsets.push_back({buf, offsets[_u8L("Tower")]}); } if ((displayed_columns & ~ColumnData::Model) > 0) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m, (total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g) / unit_conver); columns_offsets.push_back({buf, offsets[_u8L("Total")]}); } append_item(false, m_tools.m_tool_colors[0], columns_offsets); } ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(_u8L("Total cost") + ":"); ImGui::SameLine(); ::sprintf(buf, "%.2f", total_cost_all_plates); imgui.text(buf); ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1)); ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.title(_u8L("Time Estimation")); for (auto it = plate_time.begin(); it != plate_time.end(); it++) { std::vector> columns_offsets; columns_offsets.push_back({ _u8L("Plate") + " " + std::to_string(it->first), offsets[_u8L("Filament")]}); columns_offsets.push_back({ short_time(get_time_dhms(it->second)), offsets[_u8L("Model")] }); append_item(false, m_tools.m_tool_colors[0], columns_offsets); } if (plate_time.size() > 1) { // Separator ImGuiWindow* window = ImGui::GetCurrentWindow(); const ImRect separator(ImVec2(window->Pos.x + window_padding * 3, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x - window_padding * 3, window->DC.CursorPos.y + 1.0f)); ImGui::ItemSize(ImVec2(0.0f, 0.0f)); const bool item_visible = ImGui::ItemAdd(separator, 0); window->DrawList->AddLine(separator.Min, ImVec2(separator.Max.x, separator.Min.y), ImGui::GetColorU32(ImGuiCol_Separator)); std::vector> columns_offsets; columns_offsets.push_back({ _u8L("Total"), offsets[_u8L("Filament")] }); columns_offsets.push_back({ short_time(get_time_dhms(total_time_all_plates)), offsets[_u8L("Model")] }); append_item(false, m_tools.m_tool_colors[0], columns_offsets); } } ImGui::End(); ImGui::PopStyleColor(6); ImGui::PopStyleVar(3); return; } void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canvas_height, int right_margin) { if (!m_legend_enabled) return; const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); //Plater s_panel = wxGetApp().plater(); //GLCanvas3D* s_panel = wxGetApp().plater()->get_current_canvas3D(); //wxPanel* s_panel = p->status_panel; ImGuiWrapper& imgui = *wxGetApp().imgui(); //ImGuiWrapper& imgui2 = *wxGetApp().imgui(); //wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); //BBS: GUI refactor: move to the right imgui.set_next_window_pos(float(canvas_width - right_margin * m_scale), 0.0f, ImGuiCond_Always, 1.0f, 0.0f); //imgui2.set_next_window_pos(float(canvas_width - right_margin * m_scale), 1.0f, ImGuiCond_Always, 2.0f, 1.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0,0.0)); ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f,1.0f,1.0f,0.6f)); ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f)); ImGui::SetNextWindowBgAlpha(0.8f); const float max_height = 0.75f * static_cast(cnv_size.get_height()); const float child_height = 0.3333f * max_height; ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); enum class EItemType : unsigned char { Rect, Circle, Hexagon, Line, None }; const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; //BBS /*bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()));*/ bool show_estimated = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || m_view_type == EViewType::ColorPrint); const float icon_size = ImGui::GetTextLineHeight() * 0.7; //BBS GUI refactor //const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); const float percent_bar_size = 0; bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 pos_rect = ImGui::GetCursorScreenPos(); float window_padding = 4.0f * m_scale; float checkbox_offset = 0.0f; draw_list->AddRectFilled(ImVec2(pos_rect.x,pos_rect.y - ImGui::GetStyle().WindowPadding.y), ImVec2(pos_rect.x + ImGui::GetWindowWidth() + ImGui::GetFrameHeight(),pos_rect.y + ImGui::GetFrameHeight() + window_padding * 2.5), ImGui::GetColorU32(ImVec4(0,0,0,0.3))); auto append_item = [icon_size, &imgui, imperial_units, &window_padding, &draw_list, &checkbox_offset, this]( EItemType type, const Color& color, const std::vector>& columns_offsets, bool checkbox = true, bool visible = true, std::function callback = nullptr) { // render icon ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y); switch (type) { default: case EItemType::Rect: { draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale }, ImGui::GetColorU32({color[0], color[1], color[2], color[3]})); break; } case EItemType::Circle: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size + 5.0f)); draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({color[0], color[1], color[2], color[3]}), 16); break; } case EItemType::Hexagon: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size + 5.0f)); draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({color[0], color[1], color[2], color[3]}), 6); break; } case EItemType::Line: { draw_list->AddLine({pos.x + 1, pos.y + icon_size + 2}, {pos.x + icon_size - 1, pos.y + 4}, ImGui::GetColorU32({color[0], color[1], color[2], color[3]}), 3.0f); break; case EItemType::None: break; } } ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0 * m_scale, 6.0 * m_scale)); // BBS render selectable ImGui::Dummy({ 0.0, 0.0 }); ImGui::SameLine(); if (callback) { ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * m_scale); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0 * m_scale, 0.0)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.00f, 0.68f, 0.26f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(1.00f, 0.68f, 0.26f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); float max_height = 0.f; for (auto column_offset : columns_offsets) { if (ImGui::CalcTextSize(column_offset.first.c_str()).y > max_height) max_height = ImGui::CalcTextSize(column_offset.first.c_str()).y; } bool b_menu_item = ImGui::BBLMenuItem(("##" + columns_offsets[0].first).c_str(), nullptr, false, true, max_height); ImGui::PopStyleVar(2); ImGui::PopStyleColor(3); if (b_menu_item) callback(); if (checkbox) { ImGui::SameLine(checkbox_offset); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0, 0.0)); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); ImGui::Checkbox(("##" + columns_offsets[0].first).c_str(), &visible); ImGui::PopStyleColor(1); ImGui::PopStyleVar(1); } } // BBS render column item { if (callback && !checkbox && !visible) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(172 / 255.0f, 172 / 255.0f, 172 / 255.0f, 1.00f)); float dummy_size = type == EItemType::None ? window_padding * 3 : ImGui::GetStyle().ItemSpacing.x + icon_size; ImGui::SameLine(dummy_size); imgui.text(columns_offsets[0].first); for (auto i = 1; i < columns_offsets.size(); i++) { ImGui::SameLine(columns_offsets[i].second); imgui.text(columns_offsets[i].first); } if (callback && !checkbox && !visible) ImGui::PopStyleColor(1); } ImGui::PopStyleVar(1); }; auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) { auto append_range_item = [append_item](int i, float value, unsigned int decimals) { char buf[1024]; ::sprintf(buf, "%.*f", decimals, value); append_item(EItemType::Rect, Range_Colors[i], { { buf , 0} }); }; if (range.count == 1) // single item use case append_range_item(0, range.min, decimals); else if (range.count == 2) { append_range_item(static_cast(Range_Colors.size()) - 1, range.max, decimals); append_range_item(0, range.min, decimals); } else { const float step_size = range.step_size(); for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { append_range_item(i, range.get_value_at_step(i), decimals); } } }; auto append_headers = [&imgui, &window_padding](const std::vector>& title_offsets) { for (size_t i = 0; i < title_offsets.size(); i++) { ImGui::SameLine(title_offsets[i].second); imgui.bold_text(title_offsets[i].first); } ImGui::SameLine(); ImGui::Dummy({ window_padding, 0 }); ImGui::Separator(); }; auto max_width = [](const std::vector& items, const std::string& title, float extra_size = 0.0f) { float ret = ImGui::CalcTextSize(title.c_str()).x; for (const std::string& item : items) { ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x); } return ret; }; auto calculate_offsets = [max_width, window_padding, &checkbox_offset](const std::vector>>& title_columns, float extra_size = 0.0f) { const ImGuiStyle& style = ImGui::GetStyle(); std::vector offsets; offsets.push_back(max_width(title_columns[0].second, title_columns[0].first, extra_size) + 3.0f * style.ItemSpacing.x); for (size_t i = 2; i < title_columns.size(); i++) { if (title_columns[i].first == "") { offsets.push_back(offsets.back() + max_width(title_columns[i - 1].second, "") + style.ItemSpacing.x); } else if (title_columns[i].first == _u8L("Display")) { float length = ImGui::CalcTextSize(title_columns[i - 2].first.c_str()).x; float offset = offsets.back() + max_width(title_columns[i - 1].second, title_columns[i - 1].first); size_t index = offsets.size() - 2; if (index >= 0) { offset = std::max(offset, length + offsets[index]); } offsets.push_back(offset + 2.0f * style.ItemSpacing.x); } else { offsets.push_back(offsets.back() + max_width(title_columns[i - 1].second, title_columns[i - 1].first) + 2.0f * style.ItemSpacing.x); } } float average_col_width = ImGui::GetWindowWidth() / static_cast(title_columns.size()); std::vector ret; ret.push_back(0); for (size_t i = 1; i < title_columns.size(); i++) { ret.push_back(std::max(offsets[i - 1], i * average_col_width)); } if (title_columns.back().first == _u8L("Display")) { checkbox_offset = ret.back() + window_padding; } return ret; }; // BBS: no ColorChange type, use ToolChange //auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { // std::vector>> ret; // ret.reserve(custom_gcode_per_print_z.size()); // for (const auto& item : custom_gcode_per_print_z) { // if (extruder_id + 1 != static_cast(item.extruder)) // continue; // if (item.type != ColorChange) // continue; // const std::vector zs = m_layers.get_zs(); // auto lower_b = std::lower_bound(zs.begin(), zs.end(), item.print_z - epsilon()); // if (lower_b == zs.end()) // continue; // const double current_z = *lower_b; // const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); // // to avoid duplicate values, check adding values // if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) // ret.push_back({ decode_color(item.color), { previous_z, current_z } }); // } // return ret; //}; auto upto_label = [](double z) { char buf[64]; ::sprintf(buf, "%.2f", z); return _u8L("up to") + " " + std::string(buf) + " " + _u8L("mm"); }; auto above_label = [](double z) { char buf[64]; ::sprintf(buf, "%.2f", z); return _u8L("above") + " " + std::string(buf) + " " + _u8L("mm"); }; auto fromto_label = [](double z1, double z2) { char buf1[64]; ::sprintf(buf1, "%.2f", z1); char buf2[64]; ::sprintf(buf2, "%.2f", z2); return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); }; auto role_time_and_percent = [time_mode](ExtrusionRole role) { auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); }; auto move_time_and_percent = [time_mode](EMoveType move_type) { auto it = std::find_if(time_mode.moves_times.begin(), time_mode.moves_times.end(), [move_type](const std::pair& item) { return move_type == item.first; }); return (it != time_mode.moves_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); }; auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) { auto it = m_print_statistics.used_filaments_per_role.find(role); if (it == m_print_statistics.used_filaments_per_role.end()) return std::make_pair(0.0, 0.0); double koef = imperial_units ? 1000.0 / GizmoObjectManipulation::in_to_mm : 1.0; return std::make_pair(it->second.first * koef, it->second.second); }; // get used filament (meters and grams) from used volume in respect to the active extruder auto get_used_filament_from_volume = [this, imperial_units](double volume, int extruder_id) { double koef = imperial_units ? 1.0 / GizmoObjectManipulation::in_to_mm : 0.001; std::pair ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])), volume * m_filament_densities[extruder_id] * 0.001 }; return ret; }; //BBS display Color Scheme ImGui::Dummy({ window_padding, window_padding }); ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); std::wstring btn_name; if (m_fold) btn_name = ImGui::UnfoldButtonIcon + boost::nowide::widen(std::string("")); else btn_name = ImGui::FoldButtonIcon + boost::nowide::widen(std::string("")); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.68f, 0.26f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.0f, 0.68f, 0.26f, 0.78f)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); //ImGui::PushItemWidth( float button_width = ImGui::CalcTextSize(into_u8(btn_name).c_str()).x; if (ImGui::Button(into_u8(btn_name).c_str(), ImVec2(button_width, 0))) { m_fold = !m_fold; } ImGui::PopStyleColor(3); ImGui::PopStyleVar(1); ImGui::SameLine(); imgui.bold_text(_u8L("More parameters")); //push_combo_style(); ////xiamian+ ////wxBoxSizer* moreOptionsSizer = new wxBoxSizer(wxVERTICAL); //ImGui::SameLine(); //const char* view_type_value = view_type_items_str[m_view_type_sel].c_str(); //ImGuiComboFlags flags = 0; //if (ImGui::BBLBeginCombo("", view_type_value, flags)) { // ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); // //select controller // for (int i = 0; i < view_type_items_str.size(); i++) { // const bool is_selected = (m_view_type_sel == i); // if (ImGui::BBLSelectable(view_type_items_str[i].c_str(), is_selected)) { // m_fold = false; // m_view_type_sel = i; // set_view_type(view_type_items[m_view_type_sel]); // reset_visible(view_type_items[m_view_type_sel]); // // update buffers' render paths // refresh_render_paths(false, false); // update_moves_slider(); // wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); // } // if (is_selected) { // ImGui::SetItemDefaultFocus(); // } // } // ImGui::PopStyleVar(1); // ImGui::EndCombo(); //} //pop_combo_style(); //ImGui::Dummy({ window_padding, window_padding }); /* if (m_fold) { legend_height = ImGui::GetStyle().WindowPadding.y + ImGui::GetFrameHeight() + window_padding * 2.5; imgui.end(); ImGui::PopStyleColor(6); ImGui::PopStyleVar(2); return; }*/ //ImGui::Dummy({ window_padding, window_padding }); //imgui.bold_text(_u8L("Color Scheme")); //push_combo_style(); //ImGui::SameLine(); //const char* view_type_value = view_type_items_str[m_view_type_sel].c_str(); //ImGuiComboFlags flags = 0; //if (ImGui::BBLBeginCombo("", view_type_value, flags)) { // ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); // //select controller // for (int i = 0; i < view_type_items_str.size(); i++) { // const bool is_selected = (m_view_type_sel == i); // if (ImGui::BBLSelectable(view_type_items_str[i].c_str(), is_selected)) { // m_fold = false; // m_view_type_sel = i; // set_view_type(view_type_items[m_view_type_sel]); // reset_visible(view_type_items[m_view_type_sel]); // // update buffers' render paths // refresh_render_paths(false, false); // update_moves_slider(); // wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); // } // if (is_selected) { // ImGui::SetItemDefaultFocus(); // } // } // ImGui::PopStyleVar(1); // ImGui::EndCombo(); //} //pop_combo_style(); //ImGui::SameLine(); //ImGui::Dummy({ window_padding, window_padding }); // data used to properly align items in columns when showing time std::vector offsets; std::vector labels; std::vector times; std::string travel_time; std::vector percents; std::string travel_percent; std::vector model_used_filaments_m; std::vector model_used_filaments_g; std::vector used_filaments_m; std::vector used_filaments_g; double total_model_used_filament_m = 0, total_model_used_filament_g = 0; std::vector flushed_filaments_m; std::vector flushed_filaments_g; double total_flushed_filament_m = 0, total_flushed_filament_g = 0; std::vector wipe_tower_used_filaments_m; std::vector wipe_tower_used_filaments_g; double total_wipe_tower_used_filament_m = 0, total_wipe_tower_used_filament_g = 0; std::vector support_used_filaments_m; std::vector support_used_filaments_g; double total_support_used_filament_m = 0, total_support_used_filament_g = 0; struct ColumnData { enum { Model = 1, Flushed = 2, WipeTower = 4, Support = 1 << 3, }; }; int displayed_columns = 0; std::map color_print_offsets; const PrintStatistics& ps = wxGetApp().plater()->get_partplate_list().get_current_fff_print().print_statistics(); double koef = imperial_units ? GizmoObjectManipulation::in_to_mm : 1000.0; double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1; auto append_option_item = [this, append_item](EMoveType type, std::vector offsets) { auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const Color& color, const std::string& label, bool visible) { append_item(EItemType::Rect, color, { { label , offsets[0] } }, true, visible, [this, type, visible]() { m_buffers[buffer_id(type)].visible = !m_buffers[buffer_id(type)].visible; // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); }); }; const bool visible = m_buffers[buffer_id(type)].visible; if (type == EMoveType::Travel) { //BBS: only display travel time in FeatureType view append_option_item_with_type(type, Travel_Colors[0], _u8L("Travel"), visible); } else if (type == EMoveType::Seam) append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Seams], _u8L("Seams"), visible); else if (type == EMoveType::Retract) append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Retractions], _u8L("Retract"), visible); else if (type == EMoveType::Unretract) append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Unretractions], _u8L("Unretract"), visible); else if (type == EMoveType::Tool_change) append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::ToolChanges], _u8L("Filament Changes"), visible); else if (type == EMoveType::Wipe) append_option_item_with_type(type, Wipe_Color, _u8L("Wipe"), visible); }; if (!m_fold) { //ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1)); ImGui::Dummy({ window_padding, window_padding }); ImGui::Spacing(); ImGui::Dummy({ window_padding, window_padding }); //ImGui::Spacing(); //ImGui::Dummy({ window_padding, window_padding }); //imgui.bold_text(_u8L("Color Scheme")); ImGui::SameLine(); imgui.text(_u8L("Color Scheme")); push_combo_style(); ImGui::SameLine(); const char* view_type_value = view_type_items_str[m_view_type_sel].c_str(); ImGuiComboFlags flags = 0; if (ImGui::BBLBeginCombo("", view_type_value, flags)) { ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); //select controller for (int i = 0; i < view_type_items_str.size(); i++) { const bool is_selected = (m_view_type_sel == i); if (ImGui::BBLSelectable(view_type_items_str[i].c_str(), is_selected)) { m_fold = false; m_view_type_sel = i; set_view_type(view_type_items[m_view_type_sel]); reset_visible(view_type_items[m_view_type_sel]); // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); } if (is_selected) { ImGui::SetItemDefaultFocus(); } } ImGui::PopStyleVar(1); ImGui::EndCombo(); } pop_combo_style(); //ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); ImGui::Dummy({ window_padding, window_padding }); ImGui::Dummy({ window_padding, window_padding }); // used filament statistics for (size_t extruder_id : m_extruder_ids) { if (m_print_statistics.model_volumes_per_extruder.find(extruder_id) == m_print_statistics.model_volumes_per_extruder.end()) { model_used_filaments_m.push_back(0.0); model_used_filaments_g.push_back(0.0); } else { double volume = m_print_statistics.model_volumes_per_extruder.at(extruder_id); auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); model_used_filaments_m.push_back(model_used_filament_m); model_used_filaments_g.push_back(model_used_filament_g); total_model_used_filament_m += model_used_filament_m; total_model_used_filament_g += model_used_filament_g; displayed_columns |= ColumnData::Model; } } for (size_t extruder_id : m_extruder_ids) { if (m_print_statistics.wipe_tower_volumes_per_extruder.find(extruder_id) == m_print_statistics.wipe_tower_volumes_per_extruder.end()) { wipe_tower_used_filaments_m.push_back(0.0); wipe_tower_used_filaments_g.push_back(0.0); } else { double volume = m_print_statistics.wipe_tower_volumes_per_extruder.at(extruder_id); auto [wipe_tower_used_filament_m, wipe_tower_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); wipe_tower_used_filaments_m.push_back(wipe_tower_used_filament_m); wipe_tower_used_filaments_g.push_back(wipe_tower_used_filament_g); total_wipe_tower_used_filament_m += wipe_tower_used_filament_m; total_wipe_tower_used_filament_g += wipe_tower_used_filament_g; displayed_columns |= ColumnData::WipeTower; } } for (size_t extruder_id : m_extruder_ids) { if (m_print_statistics.flush_per_filament.find(extruder_id) == m_print_statistics.flush_per_filament.end()) { flushed_filaments_m.push_back(0.0); flushed_filaments_g.push_back(0.0); } else { double volume = m_print_statistics.flush_per_filament.at(extruder_id); auto [flushed_filament_m, flushed_filament_g] = get_used_filament_from_volume(volume, extruder_id); flushed_filaments_m.push_back(flushed_filament_m); flushed_filaments_g.push_back(flushed_filament_g); total_flushed_filament_m += flushed_filament_m; total_flushed_filament_g += flushed_filament_g; displayed_columns |= ColumnData::Flushed; } } for (size_t extruder_id : m_extruder_ids) { if (m_print_statistics.support_volumes_per_extruder.find(extruder_id) == m_print_statistics.support_volumes_per_extruder.end()) { support_used_filaments_m.push_back(0.0); support_used_filaments_g.push_back(0.0); } else { double volume = m_print_statistics.support_volumes_per_extruder.at(extruder_id); auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); support_used_filaments_m.push_back(used_filament_m); support_used_filaments_g.push_back(used_filament_g); total_support_used_filament_m += used_filament_m; total_support_used_filament_g += used_filament_g; displayed_columns |= ColumnData::Support; } } // extrusion paths section -> title ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); switch (m_view_type) { case EViewType::FeatureType: { // calculate offsets to align time/percentage data char buffer[64]; for (size_t i = 0; i < m_roles.size(); ++i) { ExtrusionRole role = m_roles[i]; if (role < erCount) { labels.push_back(_u8L(ExtrusionEntity::role_to_string(role))); auto [time, percent] = role_time_and_percent(role); times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); if (percent == 0) ::sprintf(buffer, "0%%"); else percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%"); percents.push_back(buffer); auto [model_used_filament_m, model_used_filament_g] = used_filament_per_role(role); //model_used_filaments_m.push_back(model_used_filament_m); //model_used_filaments_g.push_back(model_used_filament_g); memset(&buffer, 0, sizeof(buffer)); ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", model_used_filament_m); used_filaments_m.push_back(buffer); memset(&buffer, 0, sizeof(buffer)); ::sprintf(buffer, "%.2f g", model_used_filament_g); used_filaments_g.push_back(buffer); } } //BBS: get travel time and percent { auto [time, percent] = move_time_and_percent(EMoveType::Travel); travel_time = (time > 0.0f) ? short_time(get_time_dhms(time)) : ""; if (percent == 0) ::sprintf(buffer, "0%%"); else percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%"); travel_percent = buffer; } offsets = calculate_offsets({ {_u8L("Line Type"), labels}, {_u8L("Time"), times}, {_u8L("Percent"), percents}, {_u8L("Used filament"), used_filaments_m}, {"", used_filaments_g}, {_u8L("Display"), {""}} }, icon_size); append_headers({ {_u8L("Line Type"), offsets[0]}, {_u8L("Time"), offsets[1]}, {_u8L("Percent"), offsets[2]}, {_u8L("Used filament"), offsets[3]}, {"", offsets[4]}, {_u8L("Display"), offsets[5]} }); break; } case EViewType::Height: { imgui.title(_u8L("Layer Height (mm)")); break; } case EViewType::Width: { imgui.title(_u8L("Line Width (mm)")); break; } case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } case EViewType::LayerTime: { imgui.title(_u8L("Layer Time (s)")); break; } case EViewType::Tool: { // calculate used filaments data for (size_t extruder_id : m_extruder_ids) { if (m_print_statistics.model_volumes_per_extruder.find(extruder_id) == m_print_statistics.model_volumes_per_extruder.end()) continue; double volume = m_print_statistics.model_volumes_per_extruder.at(extruder_id); auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); model_used_filaments_m.push_back(model_used_filament_m); model_used_filaments_g.push_back(model_used_filament_g); } offsets = calculate_offsets({ { "Extruder NNN", {""}} }, icon_size); append_headers({ {_u8L("Filament"), offsets[0]}, {_u8L("Used filament"), offsets[1]} }); break; } case EViewType::ColorPrint: { std::vector total_filaments; char buffer[64]; ::sprintf(buffer, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", ps.total_used_filament / /*1000*/koef, ps.total_weight / unit_conver); total_filaments.push_back(buffer); std::vector>> title_columns; if (displayed_columns & ColumnData::Model) { title_columns.push_back({ _u8L("Filament"), {""} }); title_columns.push_back({ _u8L("Model"), total_filaments }); } if (displayed_columns & ColumnData::Support) { title_columns.push_back({ _u8L("Support"), total_filaments }); } if (displayed_columns & ColumnData::Flushed) { title_columns.push_back({ _u8L("Flushed"), total_filaments }); } if (displayed_columns & ColumnData::WipeTower) { title_columns.push_back({ _u8L("Tower"), total_filaments }); } if ((displayed_columns & ~ColumnData::Model) > 0) { title_columns.push_back({ _u8L("Total"), total_filaments }); } auto offsets_ = calculate_offsets(title_columns, icon_size); std::vector> title_offsets; for (int i = 0; i < offsets_.size(); i++) { title_offsets.push_back({ title_columns[i].first, offsets_[i] }); color_print_offsets[title_columns[i].first] = offsets_[i]; } append_headers(title_offsets); break; } default: { break; } } //auto append_option_item = [this, append_item](EMoveType type, std::vector offsets) { // auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const Color& color, const std::string& label, bool visible) { // append_item(EItemType::Rect, color, { { label , offsets[0] } }, true, visible, [this, type, visible]() { // m_buffers[buffer_id(type)].visible = !m_buffers[buffer_id(type)].visible; // // update buffers' render paths // refresh_render_paths(false, false); // update_moves_slider(); // wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); // }); // }; // const bool visible = m_buffers[buffer_id(type)].visible; // if (type == EMoveType::Travel) { // //BBS: only display travel time in FeatureType view // append_option_item_with_type(type, Travel_Colors[0], _u8L("Travel"), visible); // } // else if (type == EMoveType::Seam) // append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Seams], _u8L("Seams"), visible); // else if (type == EMoveType::Retract) // append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Retractions], _u8L("Retract"), visible); // else if (type == EMoveType::Unretract) // append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Unretractions], _u8L("Unretract"), visible); // else if (type == EMoveType::Tool_change) // append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::ToolChanges], _u8L("Filament Changes"), visible); // else if (type == EMoveType::Wipe) // append_option_item_with_type(type, Wipe_Color, _u8L("Wipe"), visible); // }; // extrusion paths section -> items switch (m_view_type) { case EViewType::FeatureType: { for (size_t i = 0; i < m_roles.size(); ++i) { ExtrusionRole role = m_roles[i]; if (role >= erCount) continue; const bool visible = is_visible(role); std::vector> columns_offsets; columns_offsets.push_back({ labels[i], offsets[0] }); columns_offsets.push_back({ times[i], offsets[1] }); columns_offsets.push_back({ percents[i], offsets[2] }); columns_offsets.push_back({ used_filaments_m[i], offsets[3] }); columns_offsets.push_back({ used_filaments_g[i], offsets[4] }); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], columns_offsets, true, visible, [this, role, visible]() { m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); }); } for (auto item : options_items) { if (item != EMoveType::Travel) { append_option_item(item, offsets); } else { //BBS: show travel time in FeatureType view const bool visible = m_buffers[buffer_id(item)].visible; std::vector> columns_offsets; columns_offsets.push_back({ _u8L("Travel"), offsets[0] }); columns_offsets.push_back({ travel_time, offsets[1] }); columns_offsets.push_back({ travel_percent, offsets[2] }); append_item(EItemType::Rect, Travel_Colors[0], columns_offsets, true, visible, [this, item, visible]() { m_buffers[buffer_id(item)].visible = !m_buffers[buffer_id(item)].visible; // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); }); } } break; } case EViewType::Height: { append_range(m_extrusions.ranges.height, 2); break; } case EViewType::Width: { append_range(m_extrusions.ranges.width, 2); break; } case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 0); ImGui::Spacing(); ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); offsets = calculate_offsets({ { _u8L("Options"), { _u8L("Travel")}}, { _u8L("Display"), {""}} }, icon_size); append_headers({ {_u8L("Options"), offsets[0] }, { _u8L("Display"), offsets[1]} }); const bool travel_visible = m_buffers[buffer_id(EMoveType::Travel)].visible; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 3.0f)); append_item(EItemType::None, Travel_Colors[0], { {_u8L("travel"), offsets[0] } }, true, travel_visible, [this, travel_visible]() { m_buffers[buffer_id(EMoveType::Travel)].visible = !m_buffers[buffer_id(EMoveType::Travel)].visible; // update buffers' render paths, and update m_tools.m_tool_colors and m_extrusions.ranges refresh(*m_gcode_result, wxGetApp().plater()->get_extruder_colors_from_plater_config(m_gcode_result)); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); }); ImGui::PopStyleVar(1); break; } case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } case EViewType::LayerTime: { append_range(m_extrusions.ranges.layer_duration, 1); break; } case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 2); break; } case EViewType::Tool: { // shows only extruders actually used char buf[64]; size_t i = 0; for (unsigned char extruder_id : m_extruder_ids) { ::sprintf(buf, imperial_units ? "%.2f in %.2f g" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i]); append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_id], { { _u8L("Extruder") + " " + std::to_string(extruder_id + 1), offsets[0]}, {buf, offsets[1]} }); i++; } break; } case EViewType::ColorPrint: { //BBS: replace model custom gcode with current plate custom gcode const std::vector& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z; size_t total_items = 1; // BBS: no ColorChange type, use ToolChange //for (size_t extruder_id : m_extruder_ids) { // total_items += color_print_ranges(extruder_id, custom_gcode_per_print_z).size(); //} const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; // add scrollable region, if needed if (need_scrollable) ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); // shows only extruders actually used size_t i = 0; for (auto extruder_idx : m_extruder_ids) { const bool filament_visible = m_tools.m_tool_visibles[extruder_idx]; if (i < model_used_filaments_m.size() && i < model_used_filaments_g.size()) { std::vector> columns_offsets; columns_offsets.push_back({ std::to_string(extruder_idx + 1), color_print_offsets[_u8L("Filament")] }); char buf[64]; float column_sum_m = 0.0f; float column_sum_g = 0.0f; if (displayed_columns & ColumnData::Model) { if ((displayed_columns & ~ColumnData::Model) > 0) ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver); else ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] }); column_sum_m += model_used_filaments_m[i]; column_sum_g += model_used_filaments_g[i]; } if (displayed_columns & ColumnData::Support) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m[i], support_used_filaments_g[i] / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] }); column_sum_m += support_used_filaments_m[i]; column_sum_g += support_used_filaments_g[i]; } if (displayed_columns & ColumnData::Flushed) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m[i], flushed_filaments_g[i] / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")] }); column_sum_m += flushed_filaments_m[i]; column_sum_g += flushed_filaments_g[i]; } if (displayed_columns & ColumnData::WipeTower) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m[i], wipe_tower_used_filaments_g[i] / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] }); column_sum_m += wipe_tower_used_filaments_m[i]; column_sum_g += wipe_tower_used_filaments_g[i]; } if ((displayed_columns & ~ColumnData::Model) > 0) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] }); } append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_idx], columns_offsets, false, filament_visible, [this, extruder_idx]() { m_tools.m_tool_visibles[extruder_idx] = !m_tools.m_tool_visibles[extruder_idx]; // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); }); } i++; } if (need_scrollable) ImGui::EndChild(); // Sum of all rows char buf[64]; if (m_extruder_ids.size() > 1) { // Separator ImGuiWindow* window = ImGui::GetCurrentWindow(); const ImRect separator(ImVec2(window->Pos.x + window_padding * 3, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x - window_padding * 3, window->DC.CursorPos.y + 1.0f)); ImGui::ItemSize(ImVec2(0.0f, 0.0f)); const bool item_visible = ImGui::ItemAdd(separator, 0); window->DrawList->AddLine(separator.Min, ImVec2(separator.Max.x, separator.Min.y), ImGui::GetColorU32(ImGuiCol_Separator)); std::vector> columns_offsets; columns_offsets.push_back({ _u8L("Total"), color_print_offsets[_u8L("Filament")] }); if (displayed_columns & ColumnData::Model) { if ((displayed_columns & ~ColumnData::Model) > 0) ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); else ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] }); } if (displayed_columns & ColumnData::Support) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_support_used_filament_m, total_support_used_filament_g / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] }); } if (displayed_columns & ColumnData::Flushed) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_flushed_filament_m, total_flushed_filament_g / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")] }); } if (displayed_columns & ColumnData::WipeTower) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_wipe_tower_used_filament_m, total_wipe_tower_used_filament_g / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] }); } if ((displayed_columns & ~ColumnData::Model) > 0) { ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m, (total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g) / unit_conver); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] }); } append_item(EItemType::None, m_tools.m_tool_colors[0], columns_offsets); } //BBS display filament change times ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(_u8L("Filament change times") + ":"); ImGui::SameLine(); ::sprintf(buf, "%d", m_print_statistics.total_filamentchanges); imgui.text(buf); //BBS display cost ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(_u8L("Cost") + ":"); ImGui::SameLine(); ::sprintf(buf, "%.2f", ps.total_cost); imgui.text(buf); break; } default: { break; } } // partial estimated printing time section if (m_view_type == EViewType::ColorPrint) { using Times = std::pair; using TimesList = std::vector>; // helper structure containig the data needed to render the time items struct PartialTime { enum class EType : unsigned char { Print, ColorChange, Pause }; EType type; int extruder_id; Color color1; Color color2; Times times; std::pair used_filament{ 0.0f, 0.0f }; }; using PartialTimes = std::vector; auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { PartialTimes items; //BBS: replace model custom gcode with current plate custom gcode std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z; std::vector last_color(m_extruders_count); for (size_t i = 0; i < m_extruders_count; ++i) { last_color[i] = m_tools.m_tool_colors[i]; } int last_extruder_id = 1; int color_change_idx = 0; for (const auto& time_rec : times) { switch (time_rec.first) { case CustomGCode::PausePrint: { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second }); items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); custom_gcode_per_print_z.erase(it); } break; } case CustomGCode::ColorChange: { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder - 1) }); items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); last_color[it->extruder - 1] = decode_color(it->color); last_extruder_id = it->extruder; custom_gcode_per_print_z.erase(it); } else items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id - 1) }); break; } default: { break; } } } return items; }; auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { imgui.text(_u8L("Color change")); ImGui::SameLine(); float icon_size = ImGui::GetTextLineHeight(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 pos = ImGui::GetCursorScreenPos(); pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); pos.x += icon_size; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second - times.first))); }; auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { imgui.text(_u8L("Print")); ImGui::SameLine(); float icon_size = ImGui::GetTextLineHeight(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 pos = ImGui::GetCursorScreenPos(); pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second))); ImGui::SameLine(offsets[1]); imgui.text(short_time(get_time_dhms(times.first))); if (used_filament.first > 0.0f) { char buffer[64]; ImGui::SameLine(offsets[2]); ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); imgui.text(buffer); ImGui::SameLine(offsets[3]); ::sprintf(buffer, "%.2f g", used_filament.second); imgui.text(buffer); } }; PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); if (!partial_times.empty()) { labels.clear(); times.clear(); for (const PartialTime& item : partial_times) { switch (item.type) { case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; } case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; } case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; } } times.push_back(short_time(get_time_dhms(item.times.second))); } std::string longest_used_filament_string; for (const PartialTime& item : partial_times) { if (item.used_filament.first > 0.0f) { char buffer[64]; ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); if (::strlen(buffer) > longest_used_filament_string.length()) longest_used_filament_string = buffer; } } //offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); //ImGui::Spacing(); //append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); //const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; //if (need_scrollable) // // add scrollable region // ImGui::BeginChild("events", { -1.0f, child_height }, false); //for (const PartialTime& item : partial_times) { // switch (item.type) // { // case PartialTime::EType::Print: { // append_print(item.color1, offsets, item.times, item.used_filament); // break; // } // case PartialTime::EType::Pause: { // imgui.text(_u8L("Pause")); // ImGui::SameLine(offsets[0]); // imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); // break; // } // case PartialTime::EType::ColorChange: { // append_color_change(item.color1, item.color2, offsets, item.times); // break; // } // } //} //if (need_scrollable) // ImGui::EndChild(); } } // travel paths section if (m_buffers[buffer_id(EMoveType::Travel)].visible) { switch (m_view_type) { case EViewType::Feedrate: case EViewType::Tool: case EViewType::ColorPrint: { break; } default: { // BBS GUI:refactor // title //ImGui::Spacing(); //imgui.title(_u8L("Travel")); //// items //append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); //append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); //append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); break; } } } // wipe paths section //if (m_buffers[buffer_id(EMoveType::Wipe)].visible) { // switch (m_view_type) // { // case EViewType::Feedrate: // case EViewType::Tool: // case EViewType::ColorPrint: { break; } // default: { // // title // ImGui::Spacing(); // ImGui::Dummy({ window_padding, window_padding }); // ImGui::SameLine(); // imgui.title(_u8L("Wipe")); // // items // append_item(EItemType::Line, Wipe_Color, { {_u8L("Wipe"), 0} }); // break; // } // } //} auto any_option_available = [this]() { auto available = [this](EMoveType type) { const TBuffer& buffer = m_buffers[buffer_id(type)]; return buffer.visible && buffer.has_data(); }; return available(EMoveType::Color_change) || available(EMoveType::Custom_GCode) || available(EMoveType::Pause_Print) || available(EMoveType::Retract) || available(EMoveType::Tool_change) || available(EMoveType::Unretract) || available(EMoveType::Seam); }; //auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { // const TBuffer& buffer = m_buffers[buffer_id(move_type)]; // if (buffer.visible && buffer.has_data()) // append_item(EItemType::Circle, Options_Colors[static_cast(color)], text); //}; /* BBS GUI refactor */ // options section //if (any_option_available()) { // // title // ImGui::Spacing(); // imgui.title(_u8L("Options")); // // items // add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); // add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions")); // add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams")); // add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); // add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); // add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); // add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); //} // settings section bool has_settings = false; has_settings |= !m_settings_ids.print.empty(); has_settings |= !m_settings_ids.printer.empty(); bool has_filament_settings = true; has_filament_settings &= !m_settings_ids.filament.empty(); for (const std::string& fs : m_settings_ids.filament) { has_filament_settings &= !fs.empty(); } has_settings |= has_filament_settings; //BBS: add only gcode mode bool show_settings = m_only_gcode_in_preview; //wxGetApp().is_gcode_viewer(); show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool); show_settings &= has_settings; if (show_settings) { auto calc_offset = [this]() { float ret = 0.0f; if (!m_settings_ids.printer.empty()) ret = std::max(ret, ImGui::CalcTextSize((_u8L("Printer") + std::string(":")).c_str()).x); if (!m_settings_ids.print.empty()) ret = std::max(ret, ImGui::CalcTextSize((_u8L("Print settings") + std::string(":")).c_str()).x); if (!m_settings_ids.filament.empty()) { for (unsigned char i : m_extruder_ids) { ret = std::max(ret, ImGui::CalcTextSize((_u8L("Filament") + " " + std::to_string(i + 1) + ":").c_str()).x); } } if (ret > 0.0f) ret += 2.0f * ImGui::GetStyle().ItemSpacing.x; return ret; }; ImGui::Spacing(); imgui.title(_u8L("Settings")); float offset = calc_offset(); if (!m_settings_ids.printer.empty()) { imgui.text(_u8L("Printer") + ":"); ImGui::SameLine(offset); imgui.text(m_settings_ids.printer); } if (!m_settings_ids.print.empty()) { imgui.text(_u8L("Print settings") + ":"); ImGui::SameLine(offset); imgui.text(m_settings_ids.print); } if (!m_settings_ids.filament.empty()) { for (unsigned char i : m_extruder_ids) { if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { std::string txt = _u8L("Filament"); txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); imgui.text(txt); ImGui::SameLine(offset); imgui.text(m_settings_ids.filament[i]); } } } } } // total estimated printing time section if (show_estimated) { ImGui::Dummy({ window_padding, window_padding }); ImGui::Spacing(); std::string time_title = m_view_type == EViewType::FeatureType ? _u8L("Total Estimation") : _u8L("Time Estimation"); auto can_show_mode_button = [this](PrintEstimatedStatistics::ETimeMode mode) { bool show = false; if (m_print_statistics.modes.size() > 1 && m_print_statistics.modes[static_cast(mode)].roles_times.size() > 0) { for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { if (i != static_cast(mode) && m_print_statistics.modes[i].time > 0.0f && short_time(get_time_dhms(m_print_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) { show = true; break; } } } return show; }; if (can_show_mode_button(m_time_estimate_mode)) { switch (m_time_estimate_mode) { case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } default: { assert(false); break; } } } ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1)); ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.title(time_title); //auto timecText = new wxStaticText(s_panel, wxID_ANY, wxString::FromUTF8(time_title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); //auto timecText = new wxStaticText(s_panel, wxID_ANY, time_title, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); std::string total_filament_str = _u8L("Total Filament"); std::string model_filament_str = _u8L("Model Filament"); std::string cost_str = _u8L("Cost"); std::string prepare_str = _u8L("Prepare time"); std::string print_str = _u8L("Model printing time"); std::string total_str = _u8L("Total time"); std::string end_str = _u8L("End time"); float max_len = window_padding + 2 * ImGui::GetStyle().ItemSpacing.x; if (time_mode.layers_times.empty()) max_len += ImGui::CalcTextSize(total_str.c_str()).x; else { if (m_view_type == EViewType::FeatureType) max_len += std::max(ImGui::CalcTextSize(cost_str.c_str()).x, std::max(ImGui::CalcTextSize(print_str.c_str()).x, std::max(std::max(ImGui::CalcTextSize(prepare_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x), std::max(ImGui::CalcTextSize(total_filament_str.c_str()).x, ImGui::CalcTextSize(model_filament_str.c_str()).x)))); else max_len += std::max(ImGui::CalcTextSize(print_str.c_str()).x, (std::max(ImGui::CalcTextSize(prepare_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x))); } if (m_view_type == EViewType::FeatureType) { //BBS display filament cost ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(total_filament_str + ":"); ImGui::SameLine(max_len); //BBS: use current plater's print statistics bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; char buf[64]; ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / koef); imgui.text(buf); ImGui::SameLine(); ::sprintf(buf, imperial_units ? " %.2f oz" : " %.2f g", ps.total_weight / unit_conver); imgui.text(buf); //xiamian- /*ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(model_filament_str + ":"); ImGui::SameLine(max_len); auto exlude_m = total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m; auto exlude_g = total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g; ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / koef - exlude_m); imgui.text(buf); ImGui::SameLine(); ::sprintf(buf, imperial_units ? " %.2f oz" : " %.2f g", (ps.total_weight - exlude_g) / unit_conver); imgui.text(buf);*/ //BBS: display cost of filaments //xiamian- /*ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(cost_str + ":"); ImGui::SameLine(max_len); ::sprintf(buf, "%.2f", ps.total_cost); imgui.text(buf);*/ } auto role_time = [time_mode](ExtrusionRole role) { auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); return (it != time_mode.roles_times.end()) ? it->second : 0.0f; }; //BBS: start gcode is mostly same with prepeare time //xiamian- /*if (time_mode.prepare_time != 0.0f) { ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(prepare_str + ":"); ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(time_mode.prepare_time))); }*/ //xiamian- /*ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(print_str + ":"); ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(time_mode.time - time_mode.prepare_time)));*/ ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(total_str + ":"); ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(time_mode.time))); //imgui.text(get_time_dhms(time_mode.time)); //xiamian+ ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(end_str + ":"); ImGui::SameLine(max_len); // 获取当前时间点 auto now = std::chrono::system_clock::now(); // 将当前时间点转换为time_t对象 std::time_t now_time_t = std::chrono::system_clock::to_time_t(now); // 创建tm结构体用于存储本地时间信息 std::tm local_tm = *std::localtime(&now_time_t); // 计算总秒数(包括小数部分) double total_seconds = static_cast(std::mktime(&local_tm)) + time_mode.time; // 将总秒数转换回time_t对象 std::time_t new_time_t = static_cast(total_seconds); // 更新tm结构体以反映新的时间 std::tm* new_local_tm = std::localtime(&new_time_t); // 格式化输出新的时间字符串 std::ostringstream oss; //oss << std::put_time(new_local_tm, "%Y-%m-%d %H:%M:%S"); oss << std::put_time(new_local_tm, "%Y-%m-%d %H:%M"); imgui.text(oss.str()); ImGui::SameLine(max_len); std::string abc = " "; imgui.text(abc); //ImGui::Spacing(); //shangmian+ auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { if (can_show_mode_button(mode)) { if (imgui.button(label)) { m_time_estimate_mode = mode; #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT imgui.set_requires_extra_frame(); #else wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT } } }; switch (m_time_estimate_mode) { case PrintEstimatedStatistics::ETimeMode::Normal: { show_mode_button(_L("Switch to silent mode"), PrintEstimatedStatistics::ETimeMode::Stealth); break; } case PrintEstimatedStatistics::ETimeMode::Stealth: { show_mode_button(_L("Switch to normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); break; } default : { assert(false); break; } } ImGui::Dummy({ window_padding, window_padding }); } if (m_view_type == EViewType::ColorPrint) { ImGui::Spacing(); ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); offsets = calculate_offsets({ { _u8L("Options"), { ""}}, { _u8L("Display"), {""}} }, icon_size); append_headers({ {_u8L("Options"), offsets[0] }, { _u8L("Display"), offsets[1]} }); for (auto item : options_items) append_option_item(item, offsets); } legend_height = ImGui::GetCurrentWindow()->Size.y; ImGui::Dummy({ window_padding, window_padding}); imgui.end(); ImGui::PopStyleColor(6); ImGui::PopStyleVar(2); } void GCodeViewer::push_combo_style() { ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0,8.0)); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.0f, 0.0f, 0.0f, 0.8f)); ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); } void GCodeViewer::pop_combo_style() { ImGui::PopStyleVar(3); ImGui::PopStyleColor(8); } void GCodeViewer::render_slider(int canvas_width, int canvas_height) { m_moves_slider->render(canvas_width, canvas_height); m_layers_slider->render(canvas_width, canvas_height); } #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() { static const float offset = 275.0f; ImGuiWrapper& imgui = *wxGetApp().imgui(); auto add_time = [this, &imgui](const std::string& label, int64_t time) { imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); ImGui::SameLine(offset); imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast(time) * 0.001f) + ")"); }; auto add_memory = [this, &imgui](const std::string& label, int64_t memory) { auto format_string = [memory](const std::string& units, float value) { return std::to_string(memory) + " bytes (" + Slic3r::float_to_string_decimal_point(float(memory) * value, 3) + " " + units + ")"; }; static const float kb = 1024.0f; static const float inv_kb = 1.0f / kb; static const float mb = 1024.0f * kb; static const float inv_mb = 1.0f / mb; static const float gb = 1024.0f * mb; static const float inv_gb = 1.0f / gb; imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); ImGui::SameLine(offset); if (static_cast(memory) < mb) imgui.text(format_string("KB", inv_kb)); else if (static_cast(memory) < gb) imgui.text(format_string("MB", inv_mb)); else imgui.text(format_string("GB", inv_gb)); }; auto add_counter = [this, &imgui](const std::string& label, int64_t counter) { imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); ImGui::SameLine(offset); imgui.text(std::to_string(counter)); }; imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); ImGui::SetNextWindowSizeConstraints({ 300.0f, 100.0f }, { 600.0f, 900.0f }); imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); if (ImGui::CollapsingHeader("Time")) { add_time(std::string("GCodeProcessor:"), m_statistics.results_time); ImGui::Separator(); add_time(std::string("Load:"), m_statistics.load_time); add_time(std::string(" Load vertices:"), m_statistics.load_vertices); add_time(std::string(" Smooth vertices:"), m_statistics.smooth_vertices); add_time(std::string(" Load indices:"), m_statistics.load_indices); add_time(std::string("Refresh:"), m_statistics.refresh_time); add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); } if (ImGui::CollapsingHeader("OpenGL calls")) { add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); ImGui::Separator(); add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count); add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count); } if (ImGui::CollapsingHeader("CPU memory")) { add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size); ImGui::Separator(); add_memory(std::string("Paths:"), m_statistics.paths_size); add_memory(std::string("Render paths:"), m_statistics.render_paths_size); add_memory(std::string("Models instances:"), m_statistics.models_instances_size); } if (ImGui::CollapsingHeader("GPU memory")) { add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size); add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size); add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size); ImGui::Separator(); add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size); add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size); } if (ImGui::CollapsingHeader("Other")) { add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count); add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); add_counter(std::string("Instances count:"), m_statistics.instances_count); add_counter(std::string("Batched count:"), m_statistics.batched_count); ImGui::Separator(); add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count); add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count); } imgui.end(); } #endif // ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) const { if (Slic3r::get_logging_level() >= 5) { int64_t paths_size = 0; int64_t render_paths_size = 0; for (const TBuffer& buffer : m_buffers) { paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); for (const RenderPath& path : buffer.render_paths) { render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); } } int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints); BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format("paths_size %1%, render_paths_size %2%,layers_size %3%, additional %4%\n") %paths_size %render_paths_size %layers_size %additional; BOOST_LOG_TRIVIAL(trace) << label << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" << log_memory_info(); } } GCodeViewer::Color GCodeViewer::option_color(EMoveType move_type) const { switch (move_type) { case EMoveType::Tool_change: { return Options_Colors[static_cast(EOptionsColors::ToolChanges)]; } case EMoveType::Color_change: { return Options_Colors[static_cast(EOptionsColors::ColorChanges)]; } case EMoveType::Pause_Print: { return Options_Colors[static_cast(EOptionsColors::PausePrints)]; } case EMoveType::Custom_GCode: { return Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; } case EMoveType::Retract: { return Options_Colors[static_cast(EOptionsColors::Retractions)]; } case EMoveType::Unretract: { return Options_Colors[static_cast(EOptionsColors::Unretractions)]; } case EMoveType::Seam: { return Options_Colors[static_cast(EOptionsColors::Seams)]; } default: { return { 0.0f, 0.0f, 0.0f, 1.0f }; } } } } // namespace GUI } // namespace Slic3r