Files
OrcaSlicer-KX/src/slic3r/GUI/GCodeViewer.cpp
xun.zhang 8331f8eba8 ENH: add manual grouping mode stats
1.In auto mode,display the statistics of auto mode and single
extruder
2.In manual mode,display the statistics of manual mode and auto mode
3.Support by object mode

jira:NONE

Signed-off-by: xun.zhang <xun.zhang@bambulab.com>
Change-Id: If54c4db79f33d5278c0f18d01ab0518e8660f9c7
(cherry picked from commit 2dbc5c939666e294c805ee4bf33ec09976688be3)
2025-08-24 23:18:08 +08:00

6020 lines
288 KiB
C++

#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 <imgui/imgui_internal.h>
#include <GL/glew.h>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <wx/progdlg.h>
#include <wx/numformatter.h>
#include <array>
#include <algorithm>
#include <chrono>
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");
else if (view_type == GCodeViewer::EViewType::LayerTimeLog)
return _u8L("Layer Time (log)");
return "";
}
static unsigned char buffer_id(EMoveType type) {
return static_cast<unsigned char>(type) - static_cast<unsigned char>(EMoveType::Retract);
}
static EMoveType buffer_type(unsigned char id) {
return static_cast<EMoveType>(static_cast<unsigned char>(EMoveType::Retract) + id);
}
// 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 <z-eps, z+eps>.
// Returns -1 if there is no such member.
static int find_close_layer_idx(const std::vector<double> &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<GLsizei>(vbos.size()), static_cast<const GLuint*>(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 } } });
}
ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const
{
// Input value scaled to the colors range
const float step = step_size();
float _min = min;
if(log_scale) {
value = std::log(value);
_min = std::log(min);
}
const float global_t = (step != 0.0f) ? std::max(0.0f, value - _min) / step : 0.0f; // lower limit of 0.0f
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<size_t>(static_cast<size_t>(global_t), 0, color_max_idx);
const size_t color_high_idx = std::clamp<size_t>(color_low_idx + 1, 0, color_max_idx);
// Interpolate between the low and high colors to find exactly which color the input value should get
return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast<float>(color_low_idx));
}
float GCodeViewer::Extrusions::Range::step_size() const {
if (log_scale)
{
float min_range = min;
if (min_range == 0)
min_range = 0.001f;
return (std::log(max / min_range) / (static_cast<float>(Range_Colors.size()) - 1.0f));
} else
return (max - min) / (static_cast<float>(Range_Colors.size()) - 1.0f);
}
float GCodeViewer::Extrusions::Range::get_value_at_step(int step) const {
if (!log_scale)
return min + static_cast<float>(step) * step_size();
else
return std::exp(std::log(min) + static_cast<float>(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.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<double>()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast<float>();
}
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)
{
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);
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
const Transform3d model_matrix = m_world_transform.cast<double>();
shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix);
m_model.render();
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<float>(canvas_width), static_cast<float>(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: ") + ImGui::ColorMarkerEnd;
std::string temperature = ImGui::ColorMarkerStart + _u8L("Temperature: ") + ImGui::ColorMarkerEnd;
const float item_size = imgui.calc_text_size(std::string_view{"X: 000.000 "}).x;
const float item_spacing = imgui.get_item_spacing().x;
const float window_padding = ImGui::GetStyle().WindowPadding.x;
char buf[1024];
if (true)
{
float startx2 = window_padding + item_size + item_spacing;
float startx3 = window_padding + 2*(item_size + item_spacing);
sprintf(buf, "%s%.3f", x.c_str(), position.x() - plate->get_origin().x());
ImGui::PushItemWidth(item_size);
imgui.text(buf);
ImGui::SameLine(startx2);
sprintf(buf, "%s%.3f", y.c_str(), position.y() - plate->get_origin().y());
ImGui::PushItemWidth(item_size);
imgui.text(buf);
ImGui::SameLine(startx3);
sprintf(buf, "%s%.3f", z.c_str(), position.z());
ImGui::PushItemWidth(item_size);
imgui.text(buf);
sprintf(buf, "%s%.0f", speed.c_str(), m_curr_move.feedrate);
ImGui::PushItemWidth(item_size);
imgui.text(buf);
switch (view_type) {
case EViewType::Height: {
ImGui::SameLine(startx2);
sprintf(buf, "%s%.2f", height.c_str(), m_curr_move.height);
ImGui::PushItemWidth(item_size);
imgui.text(buf);
break;
}
case EViewType::Width: {
ImGui::SameLine(startx2);
sprintf(buf, "%s%.2f", width.c_str(), m_curr_move.width);
ImGui::PushItemWidth(item_size);
imgui.text(buf);
break;
}
// case EViewType::Feedrate: {
// ImGui::SameLine(startx2);
// sprintf(buf, "%s%.0f", speed.c_str(), m_curr_move.feedrate);
// ImGui::PushItemWidth(item_size);
// imgui.text(buf);
// break;
// }
case EViewType::VolumetricRate: {
if (m_curr_move.type != EMoveType::Extrude) break;
ImGui::SameLine(startx2);
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(startx2);
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(startx2);
sprintf(buf, "%s%.0f", temperature.c_str(), m_curr_move.temperature);
ImGui::PushItemWidth(item_size);
imgui.text(buf);
break;
}
case EViewType::LayerTime:
case EViewType::LayerTimeLog: {
ImGui::SameLine(startx2);
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<size_t> &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
{
// Orca: truncate long lines(>55 characters), add "..." at the end
auto update_lines = [this](uint64_t start_id, uint64_t end_id) {
std::vector<Line> 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 original_len = m_lines_ends[id - 1] - start;
const size_t len = std::min(original_len, (size_t) 55);
std::string gline(m_file.data() + start, len);
// If original line is longer than 55 characters, truncate and append "..."
if (original_len > 55)
gline = gline.substr(0, 52) + "...";
std::string command, parameters, comment;
// extract comment
std::vector<std::string> tokens;
boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on);
command = tokens.front();
if (tokens.size() > 1)
comment = ";" + tokens.back();
// extract gcode command and parameters
if (!command.empty()) {
boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on);
command = tokens.front();
if (tokens.size() > 1) {
for (size_t i = 1; i < tokens.size(); ++i) {
parameters += " " + tokens[i];
}
}
}
ret.push_back({command, parameters, comment});
}
return ret;
};
static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT;
static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK;
static const ImVec4 COMMAND_COLOR = {0.8f, 0.8f, 0.0f, 1.0f};
static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f };
static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f };
if (!wxGetApp().show_gcode_window() || 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<uint64_t>((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<uint64_t>(m_lines_ends.size())) {
end_id = static_cast<uint64_t>(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<std::vector<Line>*>(&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<uint64_t*>(&m_selected_line_id) = curr_line_id;
*const_cast<size_t*>(&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 + 6 * m_scale, ImGuiCond_Always, 1.0f, 0.0f); // ORCA add a small gap between legend and code viewer
imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f * m_scale); // ORCA add window rounding to modernize / match style
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<float>(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;
}
}
void GCodeViewer::SequentialView::render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type)
{
if (has_render_path && m_show_marker) {
marker.set_world_position(current_position);
marker.set_world_offset(current_offset);
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
if (has_render_path)
gcode_window.render(legend_height + 2, std::max(10.f, (float)canvas_height - 40), (float)canvas_width - (float)right_margin, static_cast<uint64_t>(gcode_ids[current.last]));
}
const std::vector<ColorRGBA> 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.40f, 0.63f, 1.0f }, // erBridgeInfill
{ 0.30f, 0.50f, 0.73f, 1.0f }, // erInternalBridgeInfill
{ 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<ColorRGBA> 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<ColorRGBA> 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<ColorRGBA> GCodeViewer::Range_Colors{ {
decode_color_to_float_array("#0b2c7a"), // bluish
decode_color_to_float_array("#135985"),
decode_color_to_float_array("#1c8891"),
decode_color_to_float_array("#04d60f"),
decode_color_to_float_array("#aaf200"),
decode_color_to_float_array("#fcf903"),
decode_color_to_float_array("#f5ce0a"),
//decode_color_to_float_array("#e38820"),
decode_color_to_float_array("#d16830"),
decode_color_to_float_array("#c2523c"),
decode_color_to_float_array("#942616") // reddish
}};
const ColorRGBA GCodeViewer::Wipe_Color = ColorRGBA::YELLOW();
const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY();
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::Position;
buffer.shader = "flat";
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<ConfigOptionString>("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::ORCA_DEFAULT_PRINTER_MODEL);
}
}
}
m_sequential_view.marker.init(filename);
// initializes point sizes
std::array<int, 2> point_sizes;
::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data());
m_detected_point_sizes = { static_cast<float>(point_sizes[0]), static_cast<float>(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;
m_sequential_view.gcode_window.m_scale = scale; // ORCA
}
}
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::LayerTimeLog);
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<int> 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<BoundingBoxf3>& exclude_bounding_box, 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: data for rendering color arrangement recommendation
m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size();
std::vector<int> filament_maps = print.get_filament_maps();
std::vector<std::string> color_opt = print.config().option<ConfigOptionStrings>("filament_colour")->values;
std::vector<std::string> type_opt = print.config().option<ConfigOptionStrings>("filament_type")->values;
for (auto extruder_id : m_extruder_ids) {
if (filament_maps[extruder_id] == 1) {
m_left_extruder_filament.push_back({type_opt[extruder_id], color_opt[extruder_id], extruder_id});
} else {
m_right_extruder_filament.push_back({type_opt[extruder_id], color_opt[extruder_id], extruder_id});
}
}
//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;
m_sequential_view.m_show_marker = false;
//BBS: always load shell at preview
/*if (wxGetApp().is_editor())
{
load_shells(print);
}
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<size_t>(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<size_t>(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);
}
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<std::string>& 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
decode_colors(gcode_result.extruder_colors, m_tools.m_tool_colors);
m_tools.m_tool_visibles = std::vector<bool>(m_tools.m_tool_colors.size());
for (auto item: m_tools.m_tool_visibles) item = true;
}
else {
// update tool colors
decode_colors(str_tool_colors, m_tools.m_tool_colors);
m_tools.m_tool_visibles = std::vector<bool>(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]);
}
ColorRGBA default_color;
decode_color("#FF8000", default_color);
// 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(default_color);
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.delta_extruder > 0.005 && curr.travel_dist > 0.01) {
// Ignore very tiny extrusions from flow rate calculation, because
// it could give very imprecise result due to rounding in gcode generation
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);
m_extrusions.ranges.layer_duration_log.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::milliseconds>(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);
}
void GCodeViewer::set_shell_transparency(float alpha) { m_shells.volumes.set_transparency(alpha); }
//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<ColorRGBA>();
m_tools.m_tool_visibles = std::vector<bool>();
m_extruders_count = 0;
m_extruder_ids = std::vector<unsigned char>();
m_filament_diameters = std::vector<float>();
m_filament_densities = std::vector<float>();
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<ExtrusionRole>();
m_print_statistics.reset();
m_custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
m_sequential_view.gcode_window.reset();
m_left_extruder_filament.clear();
m_right_extruder_filament.clear();
#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
glsafe(::glEnable(GL_DEPTH_TEST));
render_shells(canvas_width, canvas_height);
if (m_roles.empty())
return;
render_toolpaths();
float legend_height = 0.0f;
render_legend(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 = SLIDER_BOTTOM_MARGIN * GCODE_VIEWER_SLIDER_SCALE;
m_sequential_view.m_show_marker = m_sequential_view.m_show_marker || (m_sequential_view.current.last != m_sequential_view.endpoints.last && !m_no_render_path);
// BBS fixed buttom margin. m_moves_slider.pos_y
m_sequential_view.render(!m_no_render_path, 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.set_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
camera.apply_viewport();
camera.set_scene_box(plate_box);
camera.set_type(Camera::EType::Ortho);
camera.set_target(center);
camera.select_view("top");
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<RenderPath>::iterator it_path, std::vector<RenderPath>::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());
shader.set_uniform(uniform_color, path.color);
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(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<int64_t>(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, int position_id, int normal_id) {
#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));
if (position_id != -1) {
glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
glsafe(::glEnableVertexAttribArray(position_id));
}
bool has_normals = buffer.vertices.normal_size_floats() > 0;
if (has_normals) {
if (normal_id != -1) {
glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
glsafe(::glEnableVertexAttribArray(normal_id));
}
}
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<size_t>(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<size_t>(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 (normal_id != -1)
glsafe(::glDisableVertexAttribArray(normal_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
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("flat");
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
int position_id = shader->get_attrib_location("v_position");
int normal_id = shader->get_attrib_location("v_normal");
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, position_id, normal_id);
//shader->set_uniform("emission_factor", 0.0f);
}
else {
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<unsigned int>(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));
if (position_id != -1) {
glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
glsafe(::glEnableVertexAttribArray(position_id));
}
bool has_normals = false;// buffer.vertices.normal_size_floats() > 0;
if (has_normals) {
if (normal_id != -1) {
glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
glsafe(::glEnableVertexAttribArray(normal_id));
}
}
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 (normal_id != -1)
glsafe(::glDisableVertexAttribArray(normal_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
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<unsigned int, 2> 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<int>(first) - static_cast<int>(m_sequential_view.last_current.first);
const int last_diff = static_cast<int>(last) - static_cast<int>(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<double> values(view.endpoints.last - view.endpoints.first + 1);
std::vector<double> 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<double>(i + 1);
if (view.gcode_ids[i] > 0) alternate_values[count] = static_cast<double>(view.gcode_ids[i]);
++count;
}
bool keep_min = m_moves_slider->GetActiveValue() == m_moves_slider->GetMinValue();
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(keep_min ? m_moves_slider->GetMinValue() : 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<uint64_t>(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<size_t>(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<size_t>(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<unsigned int>(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Shells), m_shells.visible);
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible());
flags = set_flag(flags, static_cast<unsigned int>(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<unsigned int>(Preview::OptionType::Travel)));
set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Wipe)));
set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Retractions)));
set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Unretractions)));
set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Seams)));
set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolChanges)));
set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ColorChanges)));
set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast<unsigned int>(Preview::OptionType::PausePrints)));
set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast<unsigned int>(Preview::OptionType::CustomGCodes)));
m_shells.visible = is_flag_set(static_cast<unsigned int>(Preview::OptionType::Shells));
m_sequential_view.marker.set_visible(is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolMarker)));
enable_legend(is_flag_set(static_cast<unsigned int>(Preview::OptionType::Legend)));
}
void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& 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<ColorRGBA> 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, SoftFever_VERSION);
unsigned int colors_count = 1;
for (const ColorRGBA& 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.r(), color.g(), color.b());
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, SoftFever_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<Vec3f> out_vertices;
std::vector<Vec3f> out_normals;
struct VerticesOffset
{
unsigned int vbo;
size_t offset;
};
std::vector<VerticesOffset> 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<GLsizeiptr>(t_buffer.vertices.sizes[i]), static_cast<void*>(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 ColorRGBA& 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<GLintptr>(render_path.offsets[j]),
static_cast<GLsizeiptr>(render_path.sizes[j] * sizeof(IBufferType)), static_cast<void*>(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<size_t>(indices[base + 0]) + vertices_offset;
const size_t v2 = 1 + static_cast<size_t>(indices[base + 1]) + vertices_offset;
const size_t v3 = 1 + static_cast<size_t>(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<BoundingBoxf3>& 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<MultiVertexBuffer>& vertices, const std::vector<MultiIndexBuffer>& 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 lines
auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) {
auto add_vertex = [&vertices](const Vec3f& position) {
// add position
vertices.push_back(position.x());
vertices.push_back(position.y());
vertices.push_back(position.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 &current = (i == loop_num? curr.position : curr.interpolation_points[i]);
// add previous vertex
add_vertex(previous);
// add current vertex
add_vertex(current);
}
};
//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<IBufferType>(indices.size()));
//BBS: add current index
indices.push_back(static_cast<IBufferType>(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<int, 8>& v_offsets) {
std::array<IBufferType, 8> ret = {
static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[0]),
static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[1]),
static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[2]),
static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[3]),
static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[4]),
static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[5]),
static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[6]),
static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[7])
};
return ret;
};
auto append_starting_cap_triangles = [&](IndexBuffer& indices, const std::array<IBufferType, 8>& 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<IBufferType, 8>& 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<IBufferType, 8>& 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<IBufferType, 8> first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 });
std::array<IBufferType, 8> 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::Geometry& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) {
const double width = static_cast<double>(1.5f * curr.width);
const double height = static_cast<double>(1.5f * curr.height);
const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast<double>(), Vec3d::Zero(), { width, width, height });
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose();
// append vertices
const size_t vertices_count = data.vertices_count();
for (size_t i = 0; i < vertices_count; ++i) {
// append position
const Vec3d position = trafo * data.extract_position_3(i).cast<double>();
vertices.push_back(float(position.x()));
vertices.push_back(float(position.y()));
vertices.push_back(float(position.z()));
// append normal
const Vec3d normal = normal_matrix * data.extract_normal_3(i).cast<double>();
vertices.push_back(float(normal.x()));
vertices.push_back(float(normal.y()));
vertices.push_back(float(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::Geometry& data, IndexBuffer& indices, IBufferType base_index) {
const size_t indices_count = data.indices_count();
for (size_t i = 0; i < indices_count; ++i) {
indices.push_back(static_cast<IBufferType>(data.extract_index(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-code"), "...",
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<double>());
//}
//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<double>());
//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<double>());
//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<double>());
//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<BoundingBoxf3>& 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<GCodeProcessorResult&>(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<MultiVertexBuffer> vertices(m_buffers.size());
std::vector<MultiIndexBuffer> indices(m_buffers.size());
std::vector<InstanceBuffer> instances(m_buffers.size());
std::vector<InstanceIdBuffer> instances_ids(m_buffers.size());
std::vector<InstancesOffsets> instances_offsets(m_buffers.size());
std::vector<float> options_zs;
size_t seams_count = 0;
std::vector<size_t> 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<unsigned int>(v_multibuffer.size()) - 1, 0, move_id - 1);
}
}
VertexBuffer& v_buffer = v_multibuffer.back();
switch (t_buffer.render_primitive_type)
{
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<unsigned int>(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::milliseconds>(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<size_t>().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<int64_t>(size_bytes);
m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast<int64_t>(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<unsigned int>(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::milliseconds>(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<MultiVertexBuffer>().swap(vertices);
std::vector<InstanceBuffer>().swap(instances);
std::vector<InstanceIdBuffer>().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<unsigned int, size_t>;
std::vector<CurrVertexBuffer> curr_vertex_buffers(m_buffers.size(), { 0, 0 });
// variable used to keep track of the vertex buffers ids
using VboIndexList = std::vector<unsigned int>;
std::vector<VboIndexList> 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::BatchedModel) {
Path& last_path = t_buffer.paths.back();
last_path.add_sub_path(prev, static_cast<unsigned int>(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::BatchedModel) {
Path& last_path = t_buffer.paths.back();
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, move_id - 1);
}
}
IndexBuffer& i_buffer = i_multibuffer.back();
switch (t_buffer.render_primitive_type)
{
case TBuffer::ERenderPrimitiveType::Line: {
add_indices_as_line(prev, curr, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(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<unsigned int>(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<int64_t>(size_bytes);
m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast<int64_t>(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<int64_t>(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::milliseconds>(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<MultiIndexBuffer>().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<double>(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<int> 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<unsigned int>(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::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
if (progress_dialog != nullptr)
progress_dialog->Destroy();
}
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<int> 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, false);
// 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++;
}
// Orca: disable wipe tower shell
// if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) {
// // BBS: adds wipe tower's volume
// std::vector<unsigned int> 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()) {
delete (*it);
m_shells.volumes.volumes.erase(it);
}
else
break;
}
for (GLVolume* volume : m_shells.volumes.volumes) {
volume->zoom_to_volumes = false;
volume->color.a(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) {
ColorRGBA color;
switch (m_view_type)
{
case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast<unsigned int>(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); break; }
case EViewType::LayerTimeLog: { color = m_extrusions.ranges.layer_duration_log.get_color_at(path.layer_time); 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<unsigned char>(m_tools.m_tool_colors.size()))
color = ColorRGBA::GRAY();
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 = ColorRGBA::WHITE(); 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<Statistics*>(&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<SequentialView*>(&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<std::tuple<unsigned char, unsigned int, unsigned int, unsigned int>> paths;
for (size_t b = 0; b < m_buffers.size(); ++b) {
TBuffer& buffer = const_cast<TBuffer&>(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<unsigned char>(b), path.sub_paths[j].first.b_id, static_cast<unsigned int>(i), static_cast<unsigned int>(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) {
m_no_render_path = true;
sequential_view->current.last = global_endpoints.last;
} else {
m_no_render_path = false;
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<unsigned int>(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<unsigned int>(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<GLintptr>(offset * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&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<GLintptr>(index * buffer.vertices.vertex_size_bytes()), static_cast<GLsizeiptr>(3 * sizeof(float)), static_cast<void*>(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<TBuffer&>(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;
ColorRGBA 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<unsigned int>(ibuffer_id), path_id };
if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) {
buffer.render_paths.emplace_back(key);
render_path = const_cast<RenderPath*>(&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<unsigned int>(m_sequential_view.current.first - sub_path.first.s_id);
unsigned int size_in_indices = 0;
switch (buffer.render_primitive_type)
{
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<size_t>((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<TBuffer*>(&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<TBuffer&>(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<SequentialRangeCap, 2>* sequential_range_caps = const_cast<std::array<SequentialRangeCap, 2>*>(&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<TBuffer&>(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<unsigned int>(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<IBufferType, 6> indices{ 0, 0, 0, 0, 0, 0 };
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 0) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[0])));
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 7) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[1])));
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 1) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[2])));
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 13) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&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<unsigned int>(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<IBufferType, 6> indices{ 0, 0, 0, 0, 0, 0 };
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 2) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[0])));
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 4) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[1])));
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 10) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[2])));
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 16) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&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::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
}
void GCodeViewer::render_toolpaths()
{
const Camera& camera = wxGetApp().plater()->get_camera();
const double zoom = camera.get_zoom();
auto render_as_lines = [
#if ENABLE_GCODE_VIEWER_STATISTICS
this
#endif // ENABLE_GCODE_VIEWER_STATISTICS
](std::vector<RenderPath>::iterator it_path, std::vector<RenderPath>::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());
shader.set_uniform(uniform_color, path.color);
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<RenderPath>::iterator it_path, std::vector<RenderPath>::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());
shader.set_uniform(uniform_color, path.color);
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(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<int64_t>(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, int position_id, int normal_id) {
#else
auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) {
#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 };
const 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));
if (position_id != -1) {
glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
glsafe(::glEnableVertexAttribArray(position_id));
}
const bool has_normals = buffer.vertices.normal_size_floats() > 0;
if (has_normals) {
if (normal_id != -1) {
glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
glsafe(::glEnableVertexAttribArray(normal_id));
}
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
for (auto& range : buffer.model.instances.render_ranges.ranges) {
const Range range_range = { range.offset, range.offset + range.count };
if (range_range.intersects(buffer_range)) {
shader.set_uniform("uniform_color", range.color);
const unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0;
const size_t offset_bytes = static_cast<size_t>(offset) * indices_per_instance * sizeof(IBufferType);
const Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) };
const size_t count = static_cast<size_t>(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 (normal_id != -1)
glsafe(::glDisableVertexAttribArray(normal_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
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));
};
const unsigned char begin_id = buffer_id(EMoveType::Retract);
const unsigned char end_id = buffer_id(EMoveType::Count);
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)
continue;
shader->start_using();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity());
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);
const int position_id = shader->get_attrib_location("v_position");
const int normal_id = shader->get_attrib_location("v_normal");
render_as_batched_model(buffer, *shader, position_id, normal_id);
shader->set_uniform("emission_factor", 0.0f);
}
else {
const int position_id = shader->get_attrib_location("v_position");
const int normal_id = shader->get_attrib_location("v_normal");
const 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<unsigned int>(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));
if (position_id != -1) {
glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
glsafe(::glEnableVertexAttribArray(position_id));
}
const bool has_normals = buffer.vertices.normal_size_floats() > 0;
if (has_normals) {
if (normal_id != -1) {
glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
glsafe(::glEnableVertexAttribArray(normal_id));
}
}
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::Line: {
glsafe(::glLineWidth(static_cast<GLfloat>(line_width(zoom))));
render_as_lines(it_path, buffer.render_paths.end(), *shader, uniform_color);
break;
}
case TBuffer::ERenderPrimitiveType::Triangle: {
render_as_triangles(it_path, buffer.render_paths.end(), *shader, uniform_color);
break;
}
default: { break; }
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
if (normal_id != -1)
glsafe(::glDisableVertexAttribArray(normal_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
}
shader->stop_using();
}
#if ENABLE_GCODE_VIEWER_STATISTICS
auto render_sequential_range_cap = [this, &camera]
#else
auto render_sequential_range_cap = [&camera]
#endif // ENABLE_GCODE_VIEWER_STATISTICS
(const SequentialRangeCap& cap) {
const TBuffer* buffer = cap.buffer;
GLShaderProgram* shader = wxGetApp().get_shader(buffer->shader.c_str());
if (shader == nullptr)
return;
shader->start_using();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity());
const int position_id = shader->get_attrib_location("v_position");
const int normal_id = shader->get_attrib_location("v_normal");
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo));
if (position_id != -1) {
glsafe(::glVertexAttribPointer(position_id, buffer->vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes()));
glsafe(::glEnableVertexAttribArray(position_id));
}
const bool has_normals = buffer->vertices.normal_size_floats() > 0;
if (has_normals) {
if (normal_id != -1) {
glsafe(::glVertexAttribPointer(normal_id, buffer->vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes()));
glsafe(::glEnableVertexAttribArray(normal_id));
}
}
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 (normal_id != -1)
glsafe(::glDisableVertexAttribArray(normal_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
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(int canvas_width, int canvas_height)
{
//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;
glsafe(::glDepthMask(GL_FALSE));
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("z_far", camera.get_far_z());
shader->set_uniform("z_near", camera.get_near_z());
m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, false, camera.get_view_matrix(), camera.get_projection_matrix(), {canvas_width, canvas_height});
shader->set_uniform("emission_factor", 0.0f);
shader->stop_using();
glsafe(::glDepthMask(GL_TRUE));
}
//BBS
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& 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, 8.0f * m_scale); // ORCA add window rounding to modernize / match style
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<float> filament_diameters = gcode_result_list.front()->filament_diameters;
std::vector<float> filament_densities = gcode_result_list.front()->filament_densities;
std::vector<ColorRGBA> filament_colors;
decode_colors(wxGetApp().plater()->get_extruder_colors_from_plater_config(gcode_result_list.back()), filament_colors);
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<std::string, float> offsets;
std::map<int, double> model_volume_of_extruders_all_plates; // map<extruder_idx, volume>
std::map<int, double> flushed_volume_of_extruders_all_plates; // map<extruder_idx, flushed volume>
std::map<int, double> wipe_tower_volume_of_extruders_all_plates; // map<extruder_idx, flushed volume>
std::map<int, double> support_volume_of_extruders_all_plates; // map<extruder_idx, flushed volume>
std::vector<double> model_used_filaments_m_all_plates;
std::vector<double> model_used_filaments_g_all_plates;
std::vector<double> flushed_filaments_m_all_plates;
std::vector<double> flushed_filaments_g_all_plates;
std::vector<double> wipe_tower_used_filaments_m_all_plates;
std::vector<double> wipe_tower_used_filaments_g_all_plates;
std::vector<double> support_used_filaments_m_all_plates;
std::vector<double> support_used_filaments_g_all_plates;
float total_time_all_plates = 0.0f;
float total_cost_all_plates = 0.0f;
bool show_detailed_statistics_page = false;
struct ColumnData {
enum {
Model = 1,
Flushed = 2,
WipeTower = 4,
Support = 1 << 3,
};
};
int displayed_columns = 0;
auto max_width = [](const std::vector<std::string>& 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<std::pair<std::string, std::vector<::string>>>& title_columns, float extra_size = 0.0f) {
const ImGuiStyle& style = ImGui::GetStyle();
std::vector<float> 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<float>(title_columns.size());
std::vector<float> 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](const ColorRGBA& color, const std::vector<std::pair<std::string, float>>& columns_offsets)
{
// render icon
ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y);
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 },
ImGuiWrapper::to_ImU32(color));
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 = 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);
}
}
ImGui::PopStyleVar(1);
};
auto append_headers = [&imgui](const std::vector<std::pair<std::string, float>>& 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<double, double> 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<size_t>(m_time_estimate_mode)];
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<std::pair<std::string, std::vector<::string>>> 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<std::pair<std::string, float>> 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<std::pair<std::string, float>> columns_offsets;
columns_offsets.push_back({ std::to_string(it->first + 1), offsets[_u8L("Filament")]});
char buf[64];
double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1.0;
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(filament_colors[it->first], columns_offsets);
}
i++;
}
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
ImGui::Dummy({ window_padding, window_padding });
ImGui::SameLine();
imgui.title(_u8L("Total Estimation"));
ImGui::Dummy({ window_padding, window_padding });
ImGui::SameLine();
imgui.text(_u8L("Total time") + ":");
ImGui::SameLine();
imgui.text(short_time(get_time_dhms(total_time_all_plates)));
ImGui::Dummy({ window_padding, window_padding });
ImGui::SameLine();
imgui.text(_u8L("Total cost") + ":");
ImGui::SameLine();
char buf[64];
::sprintf(buf, "%.2f", total_cost_all_plates);
imgui.text(buf);
}
ImGui::End();
ImGui::PopStyleColor(6);
ImGui::PopStyleVar(3);
return;
}
void GCodeViewer::render_legend_color_arr_recommen(float window_padding)
{
ImGuiWrapper &imgui = *wxGetApp().imgui();
auto link_text = [&](const std::string &label) {
ImVec2 wiki_part_size = ImGui::CalcTextSize(label.c_str());
ImColor HyperColor = ImColor(48, 221, 114, 255).Value;
ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value);
imgui.text(label.c_str());
ImGui::PopStyleColor();
// click behavior
if (ImGui::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true)) {
// underline
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.y -= 2.0f;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, HyperColor);
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
Plater *plater = wxGetApp().plater();
wxCommandEvent evt(EVT_OPEN_FILAMENT_MAP_SETTINGS_DIALOG);
evt.SetEventObject(plater);
wxPostEvent(plater, evt);
}
}
};
////BBS Color Arrangement Recommendation
ImGui::Dummy({ window_padding, window_padding });
ImGui::Dummy({ window_padding, window_padding });
ImGui::SameLine();
imgui.title(_u8L("Color Arrangement Recommendation"));
// BBS AMS containers
float line_height = ImGui::GetFrameHeight();
int AMS_filament_max_num = std::max(m_left_extruder_filament.size(), m_right_extruder_filament.size());
float three_words_width = imgui.calc_text_size("ABC"sv).x;
float AMS_container_height = (std::ceil(AMS_filament_max_num / 4.0f) * (three_words_width * 1.5f + line_height) + (line_height * 4));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(window_padding * 3, 0));
ImGui::BeginChild("#AMS", ImVec2(0, AMS_container_height), false, ImGuiWindowFlags_AlwaysUseWindowPadding);
{
auto stats_by_extruder = wxGetApp().plater()->get_partplate_list().get_current_fff_print().statistics_by_extruder();
// BBS save time;
imgui.text(_u8L("Since you set 1 AMS"));
ImGui::SameLine();
// BBS change button
link_text(_u8L("(change)"));
ImGui::SameLine();
imgui.text(_u8L(",this arrangement would be optimal."));
auto config = wxGetApp().plater()->get_partplate_list().get_current_fff_print().config();
auto filament_map_mode = config.filament_map_mode.value;
if (filament_map_mode == fmmAuto) {
imgui.text(from_u8((boost::format(_u8L("Info by multi extruder : %1%g filament and %2% filament changes")) % stats_by_extruder.stats_by_multi_extruder_auto.filament_flush_weight % stats_by_extruder.stats_by_multi_extruder_auto.filament_change_count).str()));
imgui.text(from_u8((boost::format(_u8L("Info by single extruder : %1%g filament and %2% filament changes")) % stats_by_extruder.stats_by_single_extruder.filament_flush_weight % stats_by_extruder.stats_by_single_extruder.filament_change_count).str()));
}
else if (filament_map_mode == fmmManual) {
imgui.text(from_u8((boost::format(_u8L("Info by manual mode : %1%g filament and %2% filament changes")) % stats_by_extruder.stats_by_multi_extruder_manual.filament_flush_weight % stats_by_extruder.stats_by_multi_extruder_manual.filament_change_count).str()));
imgui.text(from_u8((boost::format(_u8L("Info by auto mode : %1%g filament and %2% filament changes")) % stats_by_extruder.stats_by_multi_extruder_auto.filament_flush_weight % stats_by_extruder.stats_by_multi_extruder_auto.filament_change_count).str()));
}
float available_width = ImGui::GetContentRegionAvail().x;
float available_height = ImGui::GetContentRegionAvail().y;
float half_width = available_width * 0.5f;
float spacing = 18.0f * m_scale;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.3f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(window_padding * 2, window_padding));
ImDrawList *child_begin_draw_list = ImGui::GetWindowDrawList();
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
child_begin_draw_list->AddRectFilled(cursor_pos, ImVec2(cursor_pos.x + half_width, cursor_pos.y + line_height), IM_COL32(0, 0, 0, 64));
ImGui::BeginChild("#LeftAMS", ImVec2(half_width, available_height - line_height * .95f), false, ImGuiWindowFlags_AlwaysUseWindowPadding);
{
imgui.bold_text(_u8L("Left"));
ImGui::Dummy({window_padding, window_padding});
int index = 1;
for (const auto &extruder_filament : m_left_extruder_filament) {
imgui.filament_group(extruder_filament.type, extruder_filament.hex_color.c_str(), extruder_filament.filament_id);
if (index % 4 != 0) { ImGui::SameLine(0, spacing); }
index++;
}
ImGui::EndChild();
}
ImGui::SameLine();
cursor_pos = ImGui::GetCursorScreenPos();
child_begin_draw_list->AddRectFilled(cursor_pos, ImVec2(cursor_pos.x + half_width, cursor_pos.y + line_height), IM_COL32(0, 0, 0, 64));
ImGui::BeginChild("#RightAMS", ImVec2(half_width, available_height - line_height * .95f), false, ImGuiWindowFlags_AlwaysUseWindowPadding);
{
imgui.bold_text(_u8L("Right"));
ImGui::Dummy({window_padding, window_padding});
int index = 1;
for (const auto &extruder_filament : m_right_extruder_filament) {
imgui.filament_group(extruder_filament.type, extruder_filament.hex_color.c_str(), extruder_filament.filament_id);
if (index % 4 != 0) { ImGui::SameLine(0, spacing); }
index++;
}
ImGui::EndChild();
}
ImGui::PopStyleColor(1);
ImGui::PopStyleVar(1);
link_text(_u8L("Customize Arrangement"));
ImGui::EndChild();
}
ImGui::PopStyleVar(1);
}
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();
ImGuiWrapper& imgui = *wxGetApp().imgui();
//BBS: GUI refactor: move to the right
imgui.set_next_window_pos(float(canvas_width - right_margin * m_scale), 4.0f * m_scale, ImGuiCond_Always, 1.0f, 0.0f); // ORCA add a small gap to top to create seperation with main toolbar
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f * m_scale); // ORCA add window rounding to modernize / match style
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.59f, 0.53f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.59f, 0.53f, 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<float>(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<size_t>(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;
// ORCA dont use background on top bar to give modern look
//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, this](
EItemType type,
const ColorRGBA& color,
const std::vector<std::pair<std::string, float>>& columns_offsets,
bool checkbox = true,
bool visible = true,
std::function<void()> 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 },
ImGuiWrapper::to_ImU32(color));
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, ImGuiWrapper::to_ImU32(color), 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, ImGuiWrapper::to_ImU32(color), 6);
break;
}
case EItemType::Line: {
draw_list->AddLine({ pos.x + 1, pos.y + icon_size + 2 }, { pos.x + icon_size - 1, pos.y + 4 }, ImGuiWrapper::to_ImU32(color), 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.59f, 0.53f, 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(ImGui::GetWindowWidth() - ImGui::CalcTextSize(_u8L("Display").c_str()).x / 2 - ImGui::GetFrameHeight() / 2 - 2 * window_padding);
//ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0, 0.0));
//ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.59f, 0.53f, 1.00f));
//ImGui::Checkbox(("##" + columns_offsets[0].first).c_str(), &visible);
//ImGui::PopStyleVar(1);
// ORCA replace checkboxes with eye icon
ImGui::SameLine(ImGui::GetWindowWidth() - (16.f + 6.f) * m_scale - window_padding * 2 - (ImGui::GetScrollMaxY() > 0.0f ? ImGui::GetStyle().ScrollbarSize : 0));
ImGui::Text(into_u8(visible ? ImGui::VisibleIcon : ImGui::HiddenIcon).c_str(), ImVec2(16 * m_scale, 16 * m_scale));
}
}
// 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<int>(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<int>(Range_Colors.size()) - 1; i >= 0; --i) {
append_range_item(i, range.get_value_at_step(i), decimals);
}
}
};
auto append_headers = [&imgui, window_padding, this](const std::vector<std::pair<std::string, float>>& title_offsets) {
for (size_t i = 0; i < title_offsets.size(); i++) {
if (title_offsets[i].first == _u8L("Display")) { // ORCA Hide Display header
ImGui::SameLine(title_offsets[i].second);
ImGui::Dummy({16.f * m_scale, 1}); // 16(icon)
continue;
}
ImGui::SameLine(title_offsets[i].second);
imgui.bold_text(title_offsets[i].first);
}
// Ensure right padding
ImGui::SameLine();
ImGui::Dummy({window_padding, 1});
ImGui::Separator();
};
auto max_width = [](const std::vector<std::string>& 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 = [&imgui, max_width, window_padding, this](const std::vector<std::pair<std::string, std::vector<::string>>>& title_columns, float extra_size = 0.0f) {
const ImGuiStyle& style = ImGui::GetStyle();
std::vector<float> offsets;
// ORCA increase spacing for more readable format. Using direct number requires much less code change in here. GetTextLineHeight for additional spacing for icon_size
offsets.push_back(max_width(title_columns[0].second, title_columns[0].first, extra_size) + 12.f * m_scale + ImGui::GetTextLineHeight());
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) + 12.f * m_scale); // ORCA increase spacing for more readable format. Using direct number requires much less code change in here
if (title_columns.back().first == _u8L("Display")) {
//const auto preferred_offset = ImGui::GetWindowWidth() - ImGui::CalcTextSize(_u8L("Display").c_str()).x - ImGui::GetFrameHeight() / 2 - 2 * window_padding - ImGui::GetStyle().ScrollbarSize;
const auto preferred_offset = ImGui::GetWindowWidth() - (16.f - 6.f) * m_scale - ImGui::GetFrameHeight() / 2 - 2 * window_padding - (ImGui::GetScrollMaxY() > 0.0f ? ImGui::GetStyle().ScrollbarSize : 0);
if (preferred_offset > offsets.back()) {
offsets.back() = preferred_offset;
}
}
float average_col_width = ImGui::GetWindowWidth() / static_cast<float>(title_columns.size());
std::vector<float> 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 color_print_ranges = [this](unsigned char extruder_id, const std::vector<CustomGCode::Item>& custom_gcode_per_print_z) {
std::vector<std::pair<ColorRGBA, std::pair<double, double>>> ret;
ret.reserve(custom_gcode_per_print_z.size());
for (const auto& item : custom_gcode_per_print_z) {
if (extruder_id + 1 != static_cast<unsigned char>(item.extruder))
continue;
if (item.type != ColorChange)
continue;
const std::vector<double> 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))
{
ColorRGBA color;
decode_color(item.color, color);
ret.push_back({ 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) + " " + "mm";
};
auto above_label = [](double z) {
char buf[64];
::sprintf(buf, "%.2f", z);
return _u8L("above") + " " + std::string(buf) + " " + "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) + " " + "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<ExtrusionRole, float>& 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<EMoveType, float>& 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 ? GizmoObjectManipulation::in_to_mm / 1000.0 : 1.0;
double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1;
return std::make_pair(it->second.first / koef, it->second.second / unit_conver);
};
// 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<double, double> ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])),
volume * m_filament_densities[extruder_id] * 0.001 };
return ret;
};
//BBS Slicing Result title
ImGui::Dummy({ window_padding, window_padding });
ImGui::Dummy({ window_padding, window_padding });
ImGui::SameLine(window_padding * 2); // ORCA Ignores item spacing to get perfect window margins since since this part uses dummies for window padding
std::string title = _u8L("Slicing Result");
imgui.bold_text(title);
// BBS Set the width of the 8 "ABCD" words minus the "sliced result" to the spacing between the buttons and the title
float single_word_width = imgui.calc_text_size("ABCD"sv).x;
float title_width = imgui.calc_text_size(title).x;
float spacing = 18.0f * m_scale;
ImGui::SameLine(0, (single_word_width + spacing) * 8.0f - title_width);
std::wstring btn_name;
if (m_fold)
btn_name = ImGui::UnfoldButtonIcon;
else
btn_name = ImGui::FoldButtonIcon;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(84 / 255.f, 84 / 255.f, 90 / 255.f, 1.f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(84 / 255.f, 84 / 255.f, 90 / 255.f, 1.f));
float calc_padding = (ImGui::GetFrameHeight() - 16 * m_scale) / 2; // ORCA calculated padding for 16x16 icon
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(calc_padding, calc_padding)); // ORCA Center icon with frame padding
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f * m_scale); // ORCA Match button style with combo box
float button_width = 16 * m_scale + calc_padding * 2; // ORCA match buttons height with combo box
if (ImGui::Button(into_u8(btn_name).c_str(), ImVec2(button_width, button_width))) {
m_fold = !m_fold;
}
ImGui::SameLine();
const wchar_t gCodeToggle = ImGui::gCodeButtonIcon;
if (ImGui::Button(into_u8(gCodeToggle).c_str(), ImVec2(button_width, button_width))) {
wxGetApp().toggle_show_gcode_window();
wxGetApp().plater()->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
}
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
if (m_fold) {
legend_height = ImGui::GetStyle().WindowPadding.y + ImGui::GetFrameHeight() + window_padding * 2.5;
imgui.end();
ImGui::PopStyleColor(6);
ImGui::PopStyleVar(2);
return;
}
if(m_nozzle_nums > 1)
render_legend_color_arr_recommen(window_padding);
//BBS display Color Scheme
ImGui::Dummy({ window_padding, window_padding });
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 = ImGuiComboFlags_HeightLargest; // ORCA allow to fit all items to prevent scrolling on reaching last elements
if (ImGui::BBLBeginCombo("", view_type_value, flags)) {
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
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(0, window_padding); // ORCA Without (0,window_padding) it adds unnecessary item spacing after combo box
ImGui::Dummy({ window_padding, window_padding });
ImGui::Dummy({ window_padding, window_padding }); // ORCA Matches top-bottom window paddings
float window_width = ImGui::GetWindowWidth(); // ORCA Store window width
// data used to properly align items in columns when showing time
std::vector<float> offsets;
std::vector<std::string> labels;
std::vector<std::string> times;
std::string travel_time;
std::vector<std::string> percents;
std::vector<std::string> used_filaments_length;
std::vector<std::string> used_filaments_weight;
std::string travel_percent;
std::vector<double> model_used_filaments_m;
std::vector<double> model_used_filaments_g;
double total_model_used_filament_m = 0, total_model_used_filament_g = 0;
std::vector<double> flushed_filaments_m;
std::vector<double> flushed_filaments_g;
double total_flushed_filament_m = 0, total_flushed_filament_g = 0;
std::vector<double> wipe_tower_used_filaments_m;
std::vector<double> wipe_tower_used_filaments_g;
double total_wipe_tower_used_filament_m = 0, total_wipe_tower_used_filament_g = 0;
std::vector<double> support_used_filaments_m;
std::vector<double> 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<std::string, float> 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;
// 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) // ORCA remove % symbol from rows
::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);
::sprintf(buffer, imperial_units ? "%.2fin" : "%.2fm", model_used_filament_m); // ORCA dont use spacing between value and unit
used_filaments_length.push_back(buffer);
::sprintf(buffer, imperial_units ? "%.2foz" : "%.2fg", model_used_filament_g); // ORCA dont use spacing between value and unit
used_filaments_weight.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) // ORCA remove % symbol from rows
::sprintf(buffer, "0");
else
percent > 0.001 ? ::sprintf(buffer, "%.1f", percent * 100) : ::sprintf(buffer, "<0.1");
travel_percent = buffer;
}
// ORCA use % symbol for percentage and use "Usage" for "Used filaments"
offsets = calculate_offsets({ {_u8L("Line Type"), labels}, {_u8L("Time"), times}, {"%", percents}, {"", used_filaments_length}, {"", used_filaments_weight}, {_u8L("Display"), {""}}}, icon_size);
append_headers({{_u8L("Line Type"), offsets[0]}, {_u8L("Time"), offsets[1]}, {"%", offsets[2]}, {_u8L("Usage"), offsets[3]}, {_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")); break; }
case EViewType::LayerTimeLog: { imgui.title(_u8L("Layer Time (log)")); 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("Usage"), offsets[1]} });
break;
}
case EViewType::ColorPrint:
{
std::vector<std::string> 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<std::pair<std::string, std::vector<::string>>> 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<std::pair<std::string, float>> 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<float> offsets) {
auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const ColorRGBA& 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<std::pair<std::string, float>> 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_length[i], offsets[3]});
columns_offsets.push_back({used_filaments_weight[i], offsets[4]});
append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast<unsigned int>(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<std::pair<std::string, float>> 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, true); break; }
case EViewType::LayerTimeLog: { append_range(m_extrusions.ranges.layer_duration_log, true); 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<CustomGCode::Item>& 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<float>(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<std::pair<std::string, float>> 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<std::pair<std::string, float>> 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<float, float>;
using TimesList = std::vector<std::pair<CustomGCode::Type, Times>>;
// 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;
ColorRGBA color1;
ColorRGBA color2;
Times times;
std::pair<double, double> used_filament {0.0f, 0.0f};
};
using PartialTimes = std::vector<PartialTime>;
auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector<double>& used_filaments) {
PartialTimes items;
//BBS: replace model custom gcode with current plate custom gcode
std::vector<CustomGCode::Item> 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<ColorRGBA> 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], ColorRGBA::BLACK(), time_rec.second });
items.push_back({ PartialTime::EType::Pause, it->extruder, ColorRGBA::BLACK(), ColorRGBA::BLACK(), 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], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder - 1) });
ColorRGBA color;
decode_color(it->color, color);
items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], color, time_rec.second });
last_color[it->extruder - 1] = 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], ColorRGBA::BLACK(), 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 ColorRGBA& color1, const ColorRGBA& color2, const std::array<float, 4>& 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 },
ImGuiWrapper::to_ImU32(color1));
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 },
ImGuiWrapper::to_ImU32(color2));
ImGui::SameLine(offsets[0]);
imgui.text(short_time(get_time_dhms(times.second - times.first)));
};
auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array<float, 4>& offsets, const Times& times, std::pair<double, double> 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 },
ImGuiWrapper::to_ImU32(color));
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<float>(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<unsigned int>(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<unsigned char>(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]);
}
}
}
}
// Custom G-code overview
std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().is_editor() ?
wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes :
m_custom_gcode_per_print_z;
if (custom_gcode_per_print_z.size() != 0) {
float max_len = window_padding + 2 * ImGui::GetStyle().ItemSpacing.x;
ImGui::Spacing();
// Title Line
std::string cgcode_title_str = _u8L("Custom G-code");
std::string cgcode_layer_str = _u8L("Layer");
std::string cgcode_time_str = _u8L("Time");
// Types of custom gcode
std::string cgcode_pause_str = _u8L("Pause");
std::string cgcode_template_str= _u8L("Template");
std::string cgcode_toolchange_str = _u8L("Tool Change");
std::string cgcode_custom_str = _u8L("Custom");
std::string cgcode_unknown_str = _u8L("Unknown");
// Get longest String
max_len += std::max(ImGui::CalcTextSize(cgcode_title_str.c_str()).x,
std::max(ImGui::CalcTextSize(cgcode_pause_str.c_str()).x,
std::max(ImGui::CalcTextSize(cgcode_template_str.c_str()).x,
std::max(ImGui::CalcTextSize(cgcode_toolchange_str.c_str()).x,
std::max(ImGui::CalcTextSize(cgcode_custom_str.c_str()).x,
ImGui::CalcTextSize(cgcode_unknown_str.c_str()).x))))
);
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
ImGui::Dummy({window_padding, window_padding});
ImGui::SameLine();
imgui.title(cgcode_title_str,true);
ImGui::SameLine(max_len);
imgui.title(cgcode_layer_str, true);
ImGui::SameLine(max_len*1.5);
imgui.title(cgcode_time_str, false);
for (Slic3r::CustomGCode::Item custom_gcode : custom_gcode_per_print_z) {
ImGui::Dummy({window_padding, window_padding});
ImGui::SameLine();
switch (custom_gcode.type) {
case PausePrint: imgui.text(cgcode_pause_str); break;
case Template: imgui.text(cgcode_template_str); break;
case ToolChange: imgui.text(cgcode_toolchange_str); break;
case Custom: imgui.text(cgcode_custom_str); break;
default: imgui.text(cgcode_unknown_str); break;
}
ImGui::SameLine(max_len);
char buf[64];
int layer = m_layers.get_l_at(custom_gcode.print_z);
::sprintf(buf, "%d",layer );
imgui.text(buf);
ImGui::SameLine(max_len * 1.5);
std::vector<float> layer_times = m_print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].layers_times;
float custom_gcode_time = 0;
if (layer > 0)
{
for (int i = 0; i < layer-1; i++) {
custom_gcode_time += layer_times[i];
}
}
imgui.text(short_time(get_time_dhms(custom_gcode_time)));
}
}
// total estimated printing time section
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<size_t>(mode)].roles_times.size() > 0) {
for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) {
if (i != static_cast<size_t>(mode) &&
m_print_statistics.modes[i].time > 0.0f &&
short_time(get_time_dhms(m_print_statistics.modes[static_cast<size_t>(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);
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");
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);
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
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<ExtrusionRole, float>& 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
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)));
}
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)));
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; }
}
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 * m_scale); // ORCA scale rounding
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * m_scale); // ORCA scale frame size
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.59f, 0.53f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.59f, 0.53f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.00f, 0.59f, 0.53f, 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 = [&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<float>(time) * 0.001f) + ")");
};
auto add_memory = [&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<float>(memory) < mb)
imgui.text(format_string("KB", inv_kb));
else if (static_cast<float>(memory) < gb)
imgui.text(format_string("MB", inv_mb));
else
imgui.text(format_string("GB", inv_gb));
};
auto add_counter = [&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_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();
}
}
ColorRGBA GCodeViewer::option_color(EMoveType move_type) const
{
switch (move_type)
{
case EMoveType::Tool_change: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::ToolChanges)]; }
case EMoveType::Color_change: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::ColorChanges)]; }
case EMoveType::Pause_Print: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::PausePrints)]; }
case EMoveType::Custom_GCode: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::CustomGCodes)]; }
case EMoveType::Retract: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::Retractions)]; }
case EMoveType::Unretract: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::Unretractions)]; }
case EMoveType::Seam: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::Seams)]; }
default: { return { 0.0f, 0.0f, 0.0f, 1.0f }; }
}
}
} // namespace GUI
} // namespace Slic3r