Files
OrcaSlicer-KX/src/slic3r/GUI/GCodeViewer.cpp
lane.wei e9e4d75877 Update the codes to 01.01.00.10 for the formal release
1. first formal version of macos
2. add the bambu networking plugin install logic
3. auto compute the wipe volume when filament change
4. add the logic of wiping into support
5. refine the GUI layout and icons, improve the gui apperance in lots of
   small places
6. serveral improve to support
7. support AMS auto-mapping
8. disable lots of unstable features: such as params table, media file download, HMS
9. fix serveral kinds of bugs
10. update the document of building
11. ...
2022-07-22 20:35:34 +08:00

5213 lines
242 KiB
C++

#include "libslic3r/libslic3r.h"
#include "GCodeViewer.hpp"
#include "libslic3r/BuildVolume.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");
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);
}
static std::array<float, 4> decode_color(const std::string& color) {
static const float INV_255 = 1.0f / 255.0f;
std::array<float, 4> ret = { 0.0f, 0.0f, 0.0f, 1.0f };
const char* c = color.data() + 1;
if (color.size() == 7 && color.front() == '#') {
for (size_t j = 0; j < 3; ++j) {
int digit1 = hex_digit_to_int(*c++);
int digit2 = hex_digit_to_int(*c++);
if (digit1 == -1 || digit2 == -1)
break;
ret[j] = float(digit1 * 16 + digit2) * INV_255;
}
}
return ret;
}
static std::vector<std::array<float, 4>> decode_colors(const std::vector<std::string>& colors) {
std::vector<std::array<float, 4>> output(colors.size(), { 0.0f, 0.0f, 0.0f, 1.0f });
for (size_t i = 0; i < colors.size(); ++i) {
output[i] = decode_color(colors[i]);
}
return output;
}
// Round to a bin with minimum two digits resolution.
// Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster.
static float round_to_bin(const float value)
{
// assert(value > 0);
constexpr float const scale [5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f };
constexpr float const invscale [5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f };
constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f };
// Scaling factor, pointer to the tables above.
int i = 0;
// While the scaling factor is not yet large enough to get two integer digits after scaling and rounding:
for (; value < threshold[i] && i < 4; ++ i) ;
return std::round(value * scale[i]) * invscale[i];
}
// Find an index of a value in a sorted vector, which is in <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();
buffer.clear();
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);
}
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.extruder_id, move.cp_color_id, { { endpoint, endpoint } } });
}
GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const
{
// Input value scaled to the colors range
const float step = step_size();
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);
// Compute how far the value is between the low and high colors so that they can be interpolated
const float local_t = std::clamp(global_t - static_cast<float>(color_low_idx), 0.0f, 1.0f);
// Interpolate between the low and high colors to find exactly which color the input value should get
Color ret = { 0.0f, 0.0f, 0.0f, 1.0f };
for (unsigned int i = 0; i < 3; ++i) {
ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t);
}
return ret;
}
GCodeViewer::SequentialRangeCap::~SequentialRangeCap() {
if (ibo > 0)
glsafe(::glDeleteBuffers(1, &ibo));
}
void GCodeViewer::SequentialRangeCap::reset() {
if (ibo > 0)
glsafe(::glDeleteBuffers(1, &ibo));
buffer = nullptr;
ibo = 0;
vbo = 0;
color = { 0.0f, 0.0f, 0.0f, 1.0f };
}
void GCodeViewer::SequentialView::Marker::init(std::string filename)
{
if (filename.empty()) {
//m_model.init_from(stilized_arrow(16, 1.5f, 3.0f, 0.8f, 3.0f));
} else {
m_model.init_from_file(filename);
}
m_model.set_color(-1, {1.0f, 1.0f, 1.0f, 0.5f});
}
void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position)
{
m_world_position = position;
m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast<double>()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast<float>();
}
//BBS: GUI refactor: add canvas size from parameters
void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_height) const
{
if (!m_visible)
return;
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
shader->start_using();
shader->set_uniform("emission_factor", 0.0f);
glsafe(::glPushMatrix());
glsafe(::glMultMatrixf(m_world_transform.data()));
m_model.render();
glsafe(::glPopMatrix());
shader->stop_using();
glsafe(::glDisable(GL_BLEND));
static float last_window_width = 0.0f;
static size_t last_text_length = 0;
if (wxGetApp().get_mode() == ConfigOptionMode::comDevelop) {
ImGuiWrapper& imgui = *wxGetApp().imgui();
//BBS: GUI refactor: add canvas size from parameters
//Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
//imgui.set_next_window_pos(0.5f * static_cast<float>(cnv_size.get_width()), static_cast<float>(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f);
imgui.set_next_window_pos(0.5f * static_cast<float>(canvas_width), static_cast<float>(canvas_height), ImGuiCond_Always, 0.5f, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::SetNextWindowBgAlpha(0.25f);
imgui.begin(std::string("ExtruderPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Extruder position") + ":");
ImGui::SameLine();
char buf[1024];
//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;
sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x() - plate->get_origin().x(), position.y() - plate->get_origin().y(), position.z());
imgui.text(std::string(buf));
// force extra frame to automatically update window size
float width = ImGui::GetWindowWidth();
size_t length = strlen(buf);
if (width != last_window_width || length != last_text_length) {
last_window_width = width;
last_text_length = length;
#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();
}
}
void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, std::vector<size_t> &&lines_ends)
{
assert(! m_file.is_open());
if (m_file.is_open())
return;
m_filename = filename;
m_lines_ends = std::move(lines_ends);
m_selected_line_id = 0;
m_last_lines_size = 0;
try
{
m_file.open(boost::filesystem::path(m_filename));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": mapping file " << m_filename;
}
catch (...)
{
BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window.";
reset();
}
}
//BBS: GUI refactor: move to right
void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, float right, uint64_t curr_line_id) const
//void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const
{
auto update_lines = [this](uint64_t start_id, uint64_t end_id) {
std::vector<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 len = m_lines_ends[id - 1] - start;
std::string gline(m_file.data() + start, len);
std::string command;
std::string parameters;
std::string comment;
// extract comment
std::vector<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 (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0)
return;
// window height
const float wnd_height = bottom - top;
// number of visible lines
const float text_height = ImGui::CalcTextSize("0").y;
const ImGuiStyle& style = ImGui::GetStyle();
const uint64_t lines_count = static_cast<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, ImGuiCond_Always, 1.0f, 0.0f);
imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::SetNextWindowBgAlpha(0.6f);
imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
// center the text in the window by pushing down the first line
const float f_lines_count = static_cast<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;
}
}
//BBS: GUI refactor: move to the right
void GCodeViewer::SequentialView::render(float legend_height, int canvas_width, int canvas_height) const
{
marker.render(canvas_width, canvas_height);
//float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height();
// BBS
#if 0
if (wxGetApp().is_editor())
bottom -= wxGetApp().plater()->get_view_toolbar().get_height();
#endif
//gcode_window.render(legend_height, bottom, static_cast<uint64_t>(gcode_ids[current.last]));
if (wxGetApp().get_mode() == ConfigOptionMode::comDevelop) {
gcode_window.render(legend_height, (float) canvas_height, (float) canvas_width, static_cast<uint64_t>(gcode_ids[current.last]));
}
}
const std::vector<GCodeViewer::Color> GCodeViewer::Extrusion_Role_Colors {{
{ 0.90f, 0.70f, 0.70f, 1.0f }, // erNone
{ 1.00f, 0.90f, 0.30f, 1.0f }, // erPerimeter
{ 1.00f, 0.49f, 0.22f, 1.0f }, // erExternalPerimeter
{ 0.12f, 0.12f, 1.00f, 1.0f }, // erOverhangPerimeter
{ 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill
{ 0.59f, 0.33f, 0.80f, 1.0f }, // erSolidInfill
{ 0.94f, 0.25f, 0.25f, 1.0f }, // erTopSolidInfill
{ 0.40f, 0.36f, 0.78f, 1.0f }, // erBottomSurface
{ 1.00f, 0.55f, 0.41f, 1.0f }, // erIroning
{ 0.30f, 0.50f, 0.73f, 1.0f }, // erBridgeInfill
{ 1.00f, 1.00f, 1.00f, 1.0f }, // erGapFill
{ 0.00f, 0.53f, 0.43f, 1.0f }, // erSkirt
{ 0.00f, 0.23f, 0.43f, 1.0f }, // erBrim
{ 0.00f, 1.00f, 0.00f, 1.0f }, // erSupportMaterial
{ 0.00f, 0.50f, 0.00f, 1.0f }, // erSupportMaterialInterface
{ 0.00f, 0.25f, 0.00f, 1.0f }, // erSupportTransition
{ 0.70f, 0.89f, 0.67f, 1.0f }, // erWipeTower
{ 0.37f, 0.82f, 0.58f, 1.0f } // erCustom
}};
const std::vector<GCodeViewer::Color> GCodeViewer::Options_Colors {{
{ 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions
{ 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions
{ 0.900f, 0.900f, 0.900f, 1.0f }, // Seams
{ 0.758f, 0.744f, 0.389f, 1.0f }, // ToolChanges
{ 0.856f, 0.582f, 0.546f, 1.0f }, // ColorChanges
{ 0.322f, 0.942f, 0.512f, 1.0f }, // PausePrints
{ 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes
}};
const std::vector<GCodeViewer::Color> GCodeViewer::Travel_Colors {{
{ 0.219f, 0.282f, 0.609f, 1.0f }, // Move
{ 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude
{ 0.505f, 0.064f, 0.028f, 1.0f } // Retract
}};
// Normal ranges
// blue to red
const std::vector<GCodeViewer::Color> GCodeViewer::Range_Colors{{
decode_color_to_float_array("#FF00FF"), // bluish
decode_color_to_float_array("#FF55A9"),
decode_color_to_float_array("#FE8778"),
decode_color_to_float_array("#FFB847"),
decode_color_to_float_array("#FFD925"),
decode_color_to_float_array("#FFFF00"),
decode_color_to_float_array("#D8FF00"),
decode_color_to_float_array("#ADFF04"),
decode_color_to_float_array("#76FF01"),
decode_color_to_float_array("#00FF00") // reddish
}};
//const std::vector<GCodeViewer::Color> GCodeViewer::Range_Colors {{
// {0.043f, 0.173f, 0.478f, 1.0f}, // bluish
// {0.075f, 0.349f, 0.522f, 1.0f},
// {0.110f, 0.533f, 0.569f, 1.0f},
// {0.016f, 0.839f, 0.059f, 1.0f},
// {0.667f, 0.949f, 0.000f, 1.0f},
// {0.988f, 0.975f, 0.012f, 1.0f},
// {0.961f, 0.808f, 0.039f, 1.0f},
// //{0.890f, 0.533f, 0.125f, 1.0f},
// {0.820f, 0.408f, 0.188f, 1.0f},
// {0.761f, 0.322f, 0.235f, 1.0f},
// {0.581f, 0.149f, 0.087f, 1.0f} // reddish
//}};
const GCodeViewer::Color GCodeViewer::Wipe_Color = { 1.0f, 1.0f, 0.0f, 1.0f };
const GCodeViewer::Color GCodeViewer::Neutral_Color = { 0.25f, 0.25f, 0.25f, 1.0f };
GCodeViewer::GCodeViewer()
{
m_moves_slider = new IMSlider(0, 0, 0, 100, wxSL_HORIZONTAL);
m_layers_slider = new IMSlider(0, 0, 0, 100, wxSL_VERTICAL);
m_extrusions.reset_role_visibility_flags();
// m_sequential_view.skip_invisible_moves = true;
}
GCodeViewer::~GCodeViewer()
{
reset();
if (m_moves_slider) {
delete m_moves_slider;
m_moves_slider = nullptr;
}
if (m_layers_slider) {
delete m_layers_slider;
m_layers_slider = nullptr;
}
}
void GCodeViewer::init(ConfigOptionMode mode, PresetBundle* preset_bundle)
{
if (m_gl_data_initialized)
return;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": enter, m_buffers.size=%1%")
%m_buffers.size();
// initializes opengl data of TBuffers
for (size_t i = 0; i < m_buffers.size(); ++i) {
TBuffer& buffer = m_buffers[i];
EMoveType type = buffer_type(i);
switch (type)
{
default: { break; }
case EMoveType::Tool_change:
case EMoveType::Color_change:
case EMoveType::Pause_Print:
case EMoveType::Custom_GCode:
case EMoveType::Retract:
case EMoveType::Unretract:
case EMoveType::Seam: {
// if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) {
// buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel;
// buffer.shader = "gouraud_light_instanced";
// buffer.model.model.init_from(diamond(16));
// buffer.model.color = option_color(type);
// buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel;
// }
// else {
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel;
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
buffer.shader = "gouraud_light";
buffer.model.data = diamond(16);
buffer.model.color = option_color(type);
buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel;
// }
break;
}
case EMoveType::Wipe:
case EMoveType::Extrude: {
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
buffer.shader = "gouraud_light";
break;
}
case EMoveType::Travel: {
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line;
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
buffer.shader = "toolpaths_lines";
break;
}
}
set_toolpath_move_type_visible(EMoveType::Extrude, true);
}
// initializes tool marker
std::string filename;
if (preset_bundle != nullptr) {
const Preset* curr = &preset_bundle->printers.get_selected_preset();
if (curr->is_system)
filename = PresetUtils::system_printer_hotend_model(*curr);
else {
auto *printer_model = curr->config.opt<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::BBL_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::set_scale(float scale)
{
if(m_scale != scale)m_scale = scale;
}
void GCodeViewer::update_by_mode(ConfigOptionMode mode)
{
view_type_items.clear();
view_type_items_str.clear();
options_items.clear();
// BBS initialzed view_type items
view_type_items.push_back(EViewType::FeatureType);
view_type_items.push_back(EViewType::ColorPrint);
view_type_items.push_back(EViewType::Feedrate);
view_type_items.push_back(EViewType::Height);
view_type_items.push_back(EViewType::Width);
view_type_items.push_back(EViewType::VolumetricRate);
if (mode == ConfigOptionMode::comDevelop) {
view_type_items.push_back(EViewType::FanSpeed);
view_type_items.push_back(EViewType::Temperature);
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::Seam);
if (mode == ConfigOptionMode::comDevelop) {
options_items.push_back(EMoveType::Retract);
options_items.push_back(EMoveType::Unretract);
options_items.push_back(EMoveType::Tool_change);
options_items.push_back(EMoveType::Wipe);
}
}
//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, bool initialized, ConfigOptionMode mode, bool only_gcode)
{
// avoid processing if called with the same gcode_result
if (m_last_result_id == gcode_result.id) {
//BBS: add logs
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": the same id %1%, return directly, result %2% ") % m_last_result_id % (&gcode_result);
return;
}
//BBS: add logs
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": gcode result %1%, new id %2%, gcode file %3% ") % (&gcode_result) % m_last_result_id % gcode_result.filename;
// release gpu memory, if used
reset();
//BBS: add mutex for protection of gcode result
gcode_result.lock();
//BBS: add safe check
if (gcode_result.moves.size() == 0) {
//result cleaned before slicing ,should return here
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": gcode result reset before, return directly!");
gcode_result.unlock();
return;
}
//BBS: move the id to the end of reset
m_last_result_id = gcode_result.id;
m_gcode_result = &gcode_result;
m_only_gcode_in_preview = only_gcode;
if (mode == ConfigOptionMode::comDevelop) {
m_sequential_view.gcode_window.load_gcode(gcode_result.filename,
// Stealing out lines_ends should be safe because this gcode_result is processed only once (see the 1st if in this function).
std::move(const_cast<std::vector<size_t>&>(gcode_result.lines_ends)));
}
//BBS: add only gcode mode
//if (wxGetApp().is_gcode_viewer())
if (m_only_gcode_in_preview)
m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z;
m_max_print_height = gcode_result.printable_height;
load_toolpaths(gcode_result, build_volume, exclude_bounding_box);
//BBS: add mutex for protection of gcode result
if (m_layers.empty()) {
gcode_result.unlock();
return;
}
m_settings_ids = gcode_result.settings_ids;
m_filament_diameters = gcode_result.filament_diameters;
m_filament_densities = gcode_result.filament_densities;
//BBS: always load shell at preview
/*if (wxGetApp().is_editor())
{
//load_shells(print, initialized);
}
else {*/
//BBS: add only gcode mode
if (m_only_gcode_in_preview) {
Pointfs printable_area;
//BBS: add bed exclude area
Pointfs bed_exclude_area = Pointfs();
std::string texture;
std::string model;
if (!gcode_result.printable_area.empty()) {
// bed shape detected in the gcode
printable_area = gcode_result.printable_area;
const auto bundle = wxGetApp().preset_bundle;
if (bundle != nullptr && !m_settings_ids.printer.empty()) {
const Preset* preset = bundle->printers.find_preset(m_settings_ids.printer);
if (preset != nullptr) {
model = PresetUtils::system_printer_bed_model(*preset);
texture = PresetUtils::system_printer_bed_texture(*preset);
}
}
//BBS: add bed exclude area
if (!gcode_result.bed_exclude_area.empty())
bed_exclude_area = gcode_result.bed_exclude_area;
wxGetApp().plater()->set_bed_shape(printable_area, bed_exclude_area, gcode_result.printable_height, texture, model, gcode_result.printable_area.empty());
}
/*else {
// adjust printbed size in dependence of toolpaths bbox
const double margin = 10.0;
const Vec2d min(m_paths_bounding_box.min.x() - margin, m_paths_bounding_box.min.y() - margin);
const Vec2d max(m_paths_bounding_box.max.x() + margin, m_paths_bounding_box.max.y() + margin);
const Vec2d size = max - min;
printable_area = {
{ min.x(), min.y() },
{ max.x(), min.y() },
{ max.x(), min.y() + 0.442265 * size.y()},
{ max.x() - 10.0, min.y() + 0.4711325 * size.y()},
{ max.x() + 10.0, min.y() + 0.5288675 * size.y()},
{ max.x(), min.y() + 0.557735 * size.y()},
{ max.x(), max.y() },
{ min.x() + 0.557735 * size.x(), max.y()},
{ min.x() + 0.5288675 * size.x(), max.y() - 10.0},
{ min.x() + 0.4711325 * size.x(), max.y() + 10.0},
{ min.x() + 0.442265 * size.x(), max.y()},
{ min.x(), max.y() } };
}*/
}
m_print_statistics = gcode_result.print_statistics;
if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) {
const float time = m_print_statistics.modes[static_cast<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);
}
m_fold = false;
m_layers_slider->set_as_dirty();
m_moves_slider->set_as_dirty();
//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
m_tools.m_tool_colors = decode_colors(gcode_result.extruder_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
m_tools.m_tool_colors = decode_colors(str_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;
}
// ensure there are enough colors defined
while (m_tools.m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) {
m_tools.m_tool_colors.push_back(decode_color("#FF8000"));
m_tools.m_tool_visibles.push_back(true);
}
// update ranges for coloring / legend
m_extrusions.reset_ranges();
for (size_t i = 0; i < m_moves_count; ++i) {
// skip first vertex
if (i == 0)
continue;
const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
switch (curr.type)
{
case EMoveType::Extrude:
{
m_extrusions.ranges.height.update_from(round_to_bin(curr.height));
m_extrusions.ranges.width.update_from(round_to_bin(curr.width));
m_extrusions.ranges.fan_speed.update_from(curr.fan_speed);
m_extrusions.ranges.temperature.update_from(curr.temperature);
if (curr.extrusion_role != erCustom || is_visible(erCustom))
m_extrusions.ranges.volumetric_rate.update_from(round_to_bin(curr.volumetric_rate()));
[[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);
}
//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();
for (TBuffer& buffer : m_buffers) {
buffer.reset();
}
m_paths_bounding_box = BoundingBoxf3();
m_max_bounding_box = BoundingBoxf3();
m_shell_bounding_box = BoundingBoxf3();
m_max_print_height = 0.0f;
m_tools.m_tool_colors = std::vector<Color>();
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();
#if ENABLE_GCODE_VIEWER_STATISTICS
m_statistics.reset_all();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
m_contained_in_bed = true;
}
//BBS: GUI refactor: add canvas width and height
void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin)
{
#if ENABLE_GCODE_VIEWER_STATISTICS
m_statistics.reset_opengl();
m_statistics.total_instances_gpu_size = 0;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
//BBS: always render shells in preview window
render_shells();
if (m_roles.empty())
return;
glsafe(::glEnable(GL_DEPTH_TEST));
render_toolpaths();
//render_shells();
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 = 64;
//BBS always render the hotend-marker
//if (m_sequential_view.current.last != m_sequential_view.endpoints.last && !m_no_render_path) {
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
//BBS fixed buttom margin. m_moves_slider.pos_y
m_sequential_view.render(legend_height, canvas_width - right_margin, canvas_height - bottom_margin);
//}
#if ENABLE_GCODE_VIEWER_STATISTICS
render_statistics();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
//BBS render slider
render_slider(canvas_width, canvas_height);
}
#define ENABLE_CALIBRATION_THUMBNAIL_OUTPUT 0
#if ENABLE_CALIBRATION_THUMBNAIL_OUTPUT
static void debug_calibration_output_thumbnail(const ThumbnailData& thumbnail_data)
{
// debug export of generated image
wxImage image(thumbnail_data.width, thumbnail_data.height);
image.InitAlpha();
for (unsigned int r = 0; r < thumbnail_data.height; ++r)
{
unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width;
for (unsigned int c = 0; c < thumbnail_data.width; ++c)
{
unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c);
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
image.SetAlpha((int)c, (int)r, px[3]);
}
}
image.SaveFile("D:/calibrate.png", wxBITMAP_TYPE_PNG);
}
#endif
void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager)
{
int plate_idx = thumbnail_params.plate_id;
PartPlate* plate = partplate_list.get_plate(plate_idx);
BoundingBoxf3 plate_box = plate->get_bounding_box(false);
plate_box.min.z() = 0.0;
plate_box.max.z() = 0.0;
Vec3d center = plate_box.center();
#if 1
Camera camera;
camera.apply_viewport(0,0,thumbnail_data.width, thumbnail_data.height);
camera.set_scene_box(plate_box);
camera.set_type(Camera::EType::Ortho);
camera.set_target(center);
camera.select_view("top");
camera.apply_view_matrix();
camera.zoom_to_box(plate_box, 1.0f);
camera.apply_projection(plate_box);
auto render_as_triangles = [
#if ENABLE_GCODE_VIEWER_STATISTICS
this
#endif // ENABLE_GCODE_VIEWER_STATISTICS
](TBuffer &buffer, std::vector<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());
glsafe(::glUniform4fv(uniform_color, 1, static_cast<const GLfloat*>(path.color.data())));
glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
#if ENABLE_GCODE_VIEWER_STATISTICS
++m_statistics.gl_multi_triangles_calls_count;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
}
};
auto render_as_instanced_model = [
#if ENABLE_GCODE_VIEWER_STATISTICS
this
#endif // ENABLE_GCODE_VIEWER_STATISTICS
](TBuffer& buffer, GLShaderProgram& shader) {
for (auto& range : buffer.model.instances.render_ranges.ranges) {
if (range.vbo == 0 && range.count > 0) {
glsafe(::glGenBuffers(1, &range.vbo));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo));
glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
if (range.vbo > 0) {
buffer.model.model.set_color(-1, range.color);
buffer.model.model.render_instanced(range.vbo, range.count);
#if ENABLE_GCODE_VIEWER_STATISTICS
++m_statistics.gl_instanced_models_calls_count;
m_statistics.total_instances_gpu_size += static_cast<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) {
#endif // ENABLE_GCODE_VIEWER_STATISTICS
struct Range
{
unsigned int first;
unsigned int last;
bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; }
};
Range buffer_range = { 0, 0 };
size_t indices_per_instance = buffer.model.data.indices_count();
for (size_t j = 0; j < buffer.indices.size(); ++j) {
const IBuffer& i_buffer = buffer.indices[j];
buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance;
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
bool has_normals = buffer.vertices.normal_size_floats() > 0;
if (has_normals) {
glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
for (auto& range : buffer.model.instances.render_ranges.ranges) {
Range range_range = { range.offset, range.offset + range.count };
if (range_range.intersects(buffer_range)) {
shader.set_uniform("uniform_color", range.color);
unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0;
size_t offset_bytes = static_cast<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 (has_normals)
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
buffer_range.first = buffer_range.last;
}
};
unsigned char begin_id = buffer_id(EMoveType::Retract);
unsigned char end_id = buffer_id(EMoveType::Count);
BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail: begin_id %1%, end_id %2%")%begin_id %end_id;
for (unsigned char i = begin_id; i < end_id; ++i) {
TBuffer& buffer = m_buffers[i];
if (!buffer.visible || !buffer.has_data())
continue;
GLShaderProgram* shader = opengl_manager.get_shader("cali");
if (shader != nullptr) {
shader->start_using();
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) {
//shader->set_uniform("emission_factor", 0.25f);
render_as_instanced_model(buffer, *shader);
//shader->set_uniform("emission_factor", 0.0f);
}
else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
//shader->set_uniform("emission_factor", 0.25f);
render_as_batched_model(buffer, *shader);
//shader->set_uniform("emission_factor", 0.0f);
}
else {
switch (buffer.render_primitive_type) {
default: break;
}
int uniform_color = shader->get_uniform_location("uniform_color");
auto it_path = buffer.render_paths.begin();
for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast<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));
glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
bool has_normals = false;// buffer.vertices.normal_size_floats() > 0;
if (has_normals) {
glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
// Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors.
switch (buffer.render_primitive_type)
{
case TBuffer::ERenderPrimitiveType::Triangle: {
render_as_triangles(buffer, it_path, buffer.render_paths.end(), *shader, uniform_color);
break;
}
default: { break; }
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
if (has_normals)
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
}
shader->stop_using();
}
else {
BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail: can not find shader");
}
}
#endif
BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail: exit");
}
void GCodeViewer::_render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager)
{
BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail prepare: width %1%, height %2%")%w %h;
thumbnail_data.set(w, h);
if (!thumbnail_data.is_valid())
return;
//TODO bool multisample = m_multisample_allowed;
bool multisample = OpenGLManager::can_multisample();
//if (!multisample)
// glsafe(::glEnable(GL_MULTISAMPLE));
GLint max_samples;
glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples));
GLsizei num_samples = max_samples / 2;
GLuint render_fbo;
glsafe(::glGenFramebuffers(1, &render_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail prepare: max_samples %1%, multisample %2%, render_fbo %3%")%max_samples %multisample %render_fbo;
GLuint render_tex = 0;
GLuint render_tex_buffer = 0;
if (multisample) {
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
glsafe(::glGenRenderbuffers(1, &render_tex_buffer));
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer));
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h));
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer));
}
else {
glsafe(::glGenTextures(1, &render_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0));
}
GLuint render_depth;
glsafe(::glGenRenderbuffers(1, &render_depth));
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth));
if (multisample)
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h));
else
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h));
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
_render_calibration_thumbnail_internal(thumbnail_data, thumbnail_params, partplate_list, opengl_manager);
if (multisample) {
GLuint resolve_fbo;
glsafe(::glGenFramebuffers(1, &resolve_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo));
GLuint resolve_tex;
glsafe(::glGenTextures(1, &resolve_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0));
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo));
glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo));
glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo));
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
}
glsafe(::glDeleteTextures(1, &resolve_tex));
glsafe(::glDeleteFramebuffers(1, &resolve_fbo));
}
else
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
}
#if ENABLE_CALIBRATION_THUMBNAIL_OUTPUT
debug_calibration_output_thumbnail(thumbnail_data);
#endif
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
glsafe(::glDeleteRenderbuffers(1, &render_depth));
if (render_tex_buffer != 0)
glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer));
if (render_tex != 0)
glsafe(::glDeleteTextures(1, &render_tex));
glsafe(::glDeleteFramebuffers(1, &render_fbo));
//if (!multisample)
// glsafe(::glDisable(GL_MULTISAMPLE));
BOOST_LOG_TRIVIAL(info) << boost::format("render_calibration_thumbnail prepare: exit");
}
//BBS
void GCodeViewer::render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager)
{
// reset values and refresh render
int last_view_type_sel = m_view_type_sel;
EViewType last_view_type = m_view_type;
unsigned int last_role_visibility_flags = m_extrusions.role_visibility_flags;
// set color scheme to FilamentId
for (int i = 0; i < view_type_items.size(); i++) {
if (view_type_items[i] == EViewType::FilamentId) {
m_view_type_sel = i;
break;
}
}
set_view_type(EViewType::FilamentId, false);
// set m_layers_z_range to 0, 0
std::array<unsigned int, 2> tmp_layers_z_range = m_layers_z_range;
m_layers_z_range = {0, 0};
// 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;
}
m_moves_slider->SetSliderValues(values);
m_moves_slider->SetSliderAlternateValues(alternate_values);
m_moves_slider->SetMaxValue(view.endpoints.last - view.endpoints.first);
m_moves_slider->SetSelectionSpan(view.current.first - view.endpoints.first, view.current.last - view.endpoints.first);
if (set_to_max)
m_moves_slider->SetHigherValue(m_moves_slider->GetMaxValue());
}
void GCodeViewer::update_layers_slider_mode()
{
// true -> single-extruder printer profile OR
// multi-extruder printer profile , but whole model is printed by only one extruder
// false -> multi-extruder printer profile , and model is printed by several extruders
bool one_extruder_printed_model = true;
// extruder used for whole model for multi-extruder printer profile
int only_extruder = -1;
// BBS
if (wxGetApp().filaments_cnt() > 1) {
const ModelObjectPtrs &objects = wxGetApp().plater()->model().objects;
// check if whole model uses just only one extruder
if (!objects.empty()) {
const int extruder = objects[0]->config.has("extruder") ? objects[0]->config.option("extruder")->getInt() : 0;
auto is_one_extruder_printed_model = [objects, extruder]() {
for (ModelObject *object : objects) {
if (object->config.has("extruder") && object->config.option("extruder")->getInt() != extruder) return false;
for (ModelVolume *volume : object->volumes)
if ((volume->config.has("extruder") && volume->config.option("extruder")->getInt() != extruder) || !volume->mmu_segmentation_facets.empty()) return false;
for (const auto &range : object->layer_config_ranges)
if (range.second.has("extruder") && range.second.option("extruder")->getInt() != extruder) return false;
}
return true;
};
if (is_one_extruder_printed_model())
only_extruder = extruder;
else
one_extruder_printed_model = false;
}
}
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
}
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<Color> colors;
for (const RenderPath& path : t_buffer.render_paths) {
colors.push_back(path.color);
}
sort_remove_duplicates(colors);
// save materials file
boost::filesystem::path mat_filename(filename);
mat_filename.replace_extension("mtl");
CNumericLocalesSetter locales_setter;
FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing";
return;
}
fprintf(fp, "# G-Code Toolpaths Materials\n");
fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION);
unsigned int colors_count = 1;
for (const Color& color : colors) {
fprintf(fp, "\nnewmtl material_%d\n", colors_count++);
fprintf(fp, "Ka 1 1 1\n");
fprintf(fp, "Kd %g %g %g\n", color[0], color[1], color[2]);
fprintf(fp, "Ks 0 0 0\n");
}
fclose(fp);
// save geometry file
fp = boost::nowide::fopen(filename, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing";
return;
}
fprintf(fp, "# G-Code Toolpaths\n");
fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION);
fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str());
const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats();
std::vector<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 Color& color : colors) {
// save material triangles to file
fprintf(fp, "\nusemtl material_%zu\n", i + 1);
fprintf(fp, "# triangles material %zu\n", i + 1);
for (const RenderPath& render_path : t_buffer.render_paths) {
if (render_path.color != color)
continue;
const IBuffer& ibuffer = t_buffer.indices[render_path.ibuffer_id];
size_t vertices_offset = 0;
for (size_t j = 0; j < vertices_offsets.size(); ++j) {
const VerticesOffset& offset = vertices_offsets[j];
if (offset.vbo == ibuffer.vbo) {
vertices_offset = offset.offset;
break;
}
}
// get indices data from index buffer on gpu
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.ibo));
for (size_t j = 0; j < render_path.sizes.size(); ++j) {
IndexBuffer indices(render_path.sizes[j]);
glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<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 points
auto add_vertices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) {
vertices.push_back(curr.position.x());
vertices.push_back(curr.position.y());
vertices.push_back(curr.position.z());
};
auto add_indices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) {
buffer.add_path(curr, ibuffer_id, indices.size(), move_id);
indices.push_back(static_cast<IBufferType>(indices.size()));
};
// format data into the buffers to be rendered as lines
auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) {
auto add_vertex = [&vertices](const Vec3f& position, const Vec3f& normal) {
// add position
vertices.push_back(position.x());
vertices.push_back(position.y());
vertices.push_back(position.z());
// add normal
vertices.push_back(normal.x());
vertices.push_back(normal.y());
vertices.push_back(normal.z());
};
// x component of the normal to the current segment (the normal is parallel to the XY plane)
//BBS: Has modified a lot for this function to support arc move
size_t loop_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() : 0;
for (size_t i = 0; i < loop_num + 1; i++) {
const Vec3f &previous = (i == 0? prev.position : curr.interpolation_points[i-1]);
const Vec3f &current = (i == loop_num? curr.position : curr.interpolation_points[i]);
const Vec3f dir = (current - previous).normalized();
Vec3f normal(dir.y(), -dir.x(), 0.0);
normal.normalize();
// add previous vertex
add_vertex(previous, normal);
// add current vertex
add_vertex(current, normal);
}
};
//BBS: modify a lot to support arc travel
auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) {
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1);
buffer.paths.back().sub_paths.front().first.position = prev.position;
}
Path& last_path = buffer.paths.back();
size_t loop_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() : 0;
for (size_t i = 0; i < loop_num + 1; i++) {
//BBS: add previous index
indices.push_back(static_cast<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::InitializationData& 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();
for (const auto& entity : data.entities) {
// append vertices
for (size_t i = 0; i < entity.positions.size(); ++i) {
// append position
const Vec3d position = trafo * entity.positions[i].cast<double>();
vertices.push_back(static_cast<float>(position.x()));
vertices.push_back(static_cast<float>(position.y()));
vertices.push_back(static_cast<float>(position.z()));
// append normal
const Vec3d normal = normal_matrix * entity.normals[i].cast<double>();
vertices.push_back(static_cast<float>(normal.x()));
vertices.push_back(static_cast<float>(normal.y()));
vertices.push_back(static_cast<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::InitializationData& data, IndexBuffer& indices, IBufferType base_index) {
for (const auto& entity : data.entities) {
for (size_t i = 0; i < entity.indices.size(); ++i) {
indices.push_back(static_cast<IBufferType>(entity.indices[i] + base_index));
}
}
};
#if ENABLE_GCODE_VIEWER_STATISTICS
auto start_time = std::chrono::high_resolution_clock::now();
m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessorResult::MoveVertex);
m_statistics.results_time = gcode_result.time;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
m_moves_count = gcode_result.moves.size();
if (m_moves_count == 0)
return;
m_extruders_count = gcode_result.extruders_count;
unsigned int progress_count = 0;
static const unsigned int progress_threshold = 1000;
//BBS: add only gcode mode
ProgressDialog * progress_dialog = m_only_gcode_in_preview ?
new ProgressDialog(_L("Loading G-codes"), "...",
100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr;
wxBusyCursor busy;
//BBS: use convex_hull for toolpath outside check
Points pts;
// extract approximate paths bounding box from result
//BBS: add only gcode mode
for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) {
//if (wxGetApp().is_gcode_viewer()) {
//if (m_only_gcode_in_preview) {
// for the gcode viewer we need to take in account all moves to correctly size the printbed
// m_paths_bounding_box.merge(move.position.cast<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> 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;
seams_ids.push_back(i);
}
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::Point: { add_vertices_as_point(curr, v_buffer); break; }
case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; }
case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast<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 = [&seams_ids](size_t id) {
for (int i = seams_ids.size() - 1; i >= 0; --i) {
if (seams_ids[i] < id + i + 1)
return id + (size_t)i + 1;
}
return 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 - seams_ids.size());
for (size_t i = 0; i < m_moves_count - 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(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::Point &&
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::Point &&
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::Point: {
add_indices_as_point(curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
break;
}
case TBuffer::ERenderPrimitiveType::Line: {
add_indices_as_line(prev, curr, t_buffer, curr_vertex_buffer.second, static_cast<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();
// 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();
}
//BBS: always load shell when preview
void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing;
if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) {
//BBS: update force previewing logic
if (force_previewing)
m_shells.previewing = force_previewing;
//already loaded
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": already loaded, print=%1% print_id=%2%, print_modify_count=%3%, force_previewing %4%")%(&print) %m_shells.print_id %m_shells.print_modify_count %force_previewing;
return;
}
//reset shell firstly
reset_shell();
//BBS: move behind of reset_shell, to clear previous shell for empty plate
if (print.objects().empty()) {
// no shells, return
return;
}
// adds objects' volumes
// BBS: fix the issue that object_idx is not assigned as index of Model.objects array
int object_count = 0;
const ModelObjectPtrs& model_objs = wxGetApp().model().objects;
for (const PrintObject* obj : print.objects()) {
const ModelObject* model_obj = obj->model_object();
int object_idx = -1;
for (int idx = 0; idx < model_objs.size(); idx++) {
if (model_objs[idx]->id() == model_obj->id()) {
object_idx = idx;
break;
}
}
// BBS: object may be deleted when this method is called when deleting an object
if (object_idx == -1)
continue;
std::vector<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);
// adjust shells' z if raft is present
const SlicingParameters& slicing_parameters = obj->slicing_parameters();
if (slicing_parameters.object_print_z_min != 0.0) {
const Vec3d z_offset = slicing_parameters.object_print_z_min * Vec3d::UnitZ();
for (size_t i = current_volumes_count; i < m_shells.volumes.volumes.size(); ++i) {
GLVolume* v = m_shells.volumes.volumes[i];
v->set_volume_offset(v->get_volume_offset() + z_offset);
}
}
object_count++;
}
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) {
// BBS
// adds wipe tower's volume
std::vector<int> print_extruders;
for (auto print_obj : print.objects()) {
ModelObject* mo = print_obj->model_object();
for (ModelVolume* mv : mo->volumes) {
std::vector<int> volume_extruders = mv->get_extruders();
print_extruders.insert(print_extruders.end(), volume_extruders.begin(), volume_extruders.end());
}
}
std::sort(print_extruders.begin(), print_extruders.end());
auto it_end = std::unique(print_extruders.begin(), print_extruders.end());
print_extruders.resize(std::distance(print_extruders.begin(), it_end));
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 (extruders_count > 1 && config.enable_prime_tower && (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[3] = 0.5f;
volume->force_native_color = true;
volume->set_render_color();
//BBS: add shell bounding box logic
m_shell_bounding_box.merge(volume->transformed_bounding_box());
}
//BBS: always load shell when preview
m_shells.print_id = print.id().id;
m_shells.print_modify_count = print.get_modified_count();
m_shells.previewing = true;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": shell loaded, id change to %1%, modify_count %2%, object count %3%, glvolume count %4%")
% m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size();
}
void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const
{
#if ENABLE_GCODE_VIEWER_STATISTICS
auto start_time = std::chrono::high_resolution_clock::now();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": enter, m_buffers size %1%!")%m_buffers.size();
auto extrusion_color = [this](const Path& path) {
Color color;
switch (m_view_type)
{
case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast<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::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 = { 0.5f, 0.5f, 0.5f, 1.0f };
else
color = m_tools.m_tool_colors[path.cp_color_id];
break;
}
case EViewType::FilamentId: {
float id = float(path.extruder_id)/256;
float role = float(path.role) / 256;
color = {id, role, id, 1.0f};
break;
}
default: { color = { 1.0f, 1.0f, 1.0f, 1.0f }; break; }
}
return color;
};
auto travel_color = [](const Path& path) {
return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ :
((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ :
Travel_Colors[0] /* Move */);
};
auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) {
auto in_layers_range = [this, min_id, max_id](size_t id) {
return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last;
};
return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id);
};
//BBS
auto is_extruder_in_layer_range = [this](const Path& path, size_t extruder_id) {
return path.extruder_id == extruder_id;
};
auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) {
const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)];
if (path_id >= buffer.paths.size())
return false;
Path path = buffer.paths[path_id];
size_t first = path_id;
size_t last = path_id;
// check adjacent paths
while (first > 0 && path.sub_paths.front().first.position.isApprox(buffer.paths[first - 1].sub_paths.back().last.position)) {
--first;
path.sub_paths.front().first = buffer.paths[first].sub_paths.front().first;
}
while (last < buffer.paths.size() - 1 && path.sub_paths.back().last.position.isApprox(buffer.paths[last + 1].sub_paths.front().first.position)) {
++last;
path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last;
}
const size_t min_s_id = m_layers.get_endpoints_at(min_id).first;
const size_t max_s_id = m_layers.get_endpoints_at(max_id).last;
return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) ||
(min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id);
};
#if ENABLE_GCODE_VIEWER_STATISTICS
Statistics* statistics = const_cast<Statistics*>(&m_statistics);
statistics->render_paths_size = 0;
statistics->models_instances_size = 0;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
const bool top_layer_only = true;
SequentialView::Endpoints global_endpoints = { m_moves_count , 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;
if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count;
// 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;
} else {
m_no_render_path = false;
}
if (!m_no_render_path) {
sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last;
} else {
sequential_view->current.last = sequential_view->current.first;
}
// 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;
Color color;
switch (path.type)
{
case EMoveType::Tool_change:
case EMoveType::Color_change:
case EMoveType::Pause_Print:
case EMoveType::Custom_GCode:
case EMoveType::Retract:
case EMoveType::Unretract:
case EMoveType::Seam: { color = option_color(path.type); break; }
case EMoveType::Extrude: {
if (!top_layer_only ||
m_sequential_view.current.last == global_endpoints.last ||
is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1]))
color = extrusion_color(path);
else
color = Neutral_Color;
break;
}
case EMoveType::Travel: {
if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1]))
color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool) ? extrusion_color(path) : travel_color(path);
else
color = Neutral_Color;
break;
}
case EMoveType::Wipe: { color = Wipe_Color; break; }
default: { color = { 0.0f, 0.0f, 0.0f, 1.0f }; break; }
}
RenderPath key{ tbuffer_id, color, static_cast<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::Point: {
size_in_indices = buffer.indices_per_segment();
break;
}
case TBuffer::ERenderPrimitiveType::Line:
case TBuffer::ERenderPrimitiveType::Triangle: {
// BBS: modify to support moves which has internal point
size_t max_s_id = std::min(m_sequential_view.current.last, sub_path.last.s_id);
size_t min_s_id = std::max(m_sequential_view.current.first, sub_path.first.s_id);
unsigned int segments_count = max_s_id - min_s_id;
for (size_t i = min_s_id + 1; i < max_s_id + 1; i++) {
size_t move_id = m_ssid_to_moveid_map[i];
const GCodeProcessorResult::MoveVertex& curr = m_gcode_result->moves[move_id];
if (curr.is_arc_move()) {
segments_count += curr.interpolation_points.size();
}
}
size_in_indices = buffer.indices_per_segment() * segments_count;
break;
}
default: { break; }
}
if (size_in_indices == 0)
continue;
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
if (sub_path_id == 0 && delta_1st == 0)
size_in_indices += 6; // add 2 triangles for starting cap
if (sub_path_id == path.sub_paths.size() - 1 && path.sub_paths.back().last.s_id <= m_sequential_view.current.last)
size_in_indices += 6; // add 2 triangles for ending cap
if (delta_1st > 0)
size_in_indices -= 6; // remove 2 triangles for corner cap
}
render_path->sizes.push_back(size_in_indices);
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
delta_1st *= buffer.indices_per_segment();
if (delta_1st > 0) {
delta_1st += 6; // skip 2 triangles for corner cap
if (sub_path_id == 0)
delta_1st += 6; // skip 2 triangles for starting cap
}
}
render_path->offsets.push_back(static_cast<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()
{
#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
float point_size = 20.0f;
#else
float point_size = 0.8f;
#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
std::array<float, 4> light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f };
const Camera& camera = wxGetApp().plater()->get_camera();
double zoom = camera.get_zoom();
const std::array<int, 4>& viewport = camera.get_viewport();
float near_plane_height = camera.get_type() == Camera::EType::Perspective ? static_cast<float>(viewport[3]) / (2.0f * static_cast<float>(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) :
static_cast<float>(viewport[3]) * 0.0005;
auto shader_init_as_points = [zoom, point_size, near_plane_height](GLShaderProgram& shader) {
#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
shader.set_uniform("use_fixed_screen_size", 1);
#else
shader.set_uniform("use_fixed_screen_size", 0);
#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
shader.set_uniform("zoom", zoom);
shader.set_uniform("percent_outline_radius", 0.0f);
shader.set_uniform("percent_center_radius", 0.33f);
shader.set_uniform("point_size", point_size);
shader.set_uniform("near_plane_height", near_plane_height);
};
auto render_as_points = [
#if ENABLE_GCODE_VIEWER_STATISTICS
this
#endif // ENABLE_GCODE_VIEWER_STATISTICS
](std::vector<RenderPath>::iterator it_path, std::vector<RenderPath>::iterator it_end, GLShaderProgram& shader, int uniform_color) {
glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE));
glsafe(::glEnable(GL_POINT_SPRITE));
for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
const RenderPath& path = *it;
// Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415.
assert(! path.sizes.empty());
assert(! path.offsets.empty());
glsafe(::glUniform4fv(uniform_color, 1, static_cast<const GLfloat*>(path.color.data())));
glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
#if ENABLE_GCODE_VIEWER_STATISTICS
++m_statistics.gl_multi_points_calls_count;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
}
glsafe(::glDisable(GL_POINT_SPRITE));
glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE));
};
auto shader_init_as_lines = [light_intensity](GLShaderProgram &shader) {
shader.set_uniform("light_intensity", light_intensity);
};
auto render_as_lines = [
#if ENABLE_GCODE_VIEWER_STATISTICS
this
#endif // ENABLE_GCODE_VIEWER_STATISTICS
](std::vector<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());
glsafe(::glUniform4fv(uniform_color, 1, static_cast<const GLfloat*>(path.color.data())));
glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
#if ENABLE_GCODE_VIEWER_STATISTICS
++m_statistics.gl_multi_lines_calls_count;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
}
};
auto render_as_triangles = [
#if ENABLE_GCODE_VIEWER_STATISTICS
this
#endif // ENABLE_GCODE_VIEWER_STATISTICS
](std::vector<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());
glsafe(::glUniform4fv(uniform_color, 1, static_cast<const GLfloat*>(path.color.data())));
glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
#if ENABLE_GCODE_VIEWER_STATISTICS
++m_statistics.gl_multi_triangles_calls_count;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
}
};
auto render_as_instanced_model = [
#if ENABLE_GCODE_VIEWER_STATISTICS
this
#endif // ENABLE_GCODE_VIEWER_STATISTICS
](TBuffer& buffer, GLShaderProgram & shader) {
for (auto& range : buffer.model.instances.render_ranges.ranges) {
if (range.vbo == 0 && range.count > 0) {
glsafe(::glGenBuffers(1, &range.vbo));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo));
glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
if (range.vbo > 0) {
buffer.model.model.set_color(-1, range.color);
buffer.model.model.render_instanced(range.vbo, range.count);
#if ENABLE_GCODE_VIEWER_STATISTICS
++m_statistics.gl_instanced_models_calls_count;
m_statistics.total_instances_gpu_size += static_cast<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) {
#endif // ENABLE_GCODE_VIEWER_STATISTICS
struct Range
{
unsigned int first;
unsigned int last;
bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; }
};
Range buffer_range = { 0, 0 };
size_t indices_per_instance = buffer.model.data.indices_count();
for (size_t j = 0; j < buffer.indices.size(); ++j) {
const IBuffer& i_buffer = buffer.indices[j];
buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance;
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
bool has_normals = buffer.vertices.normal_size_floats() > 0;
if (has_normals) {
glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
for (auto& range : buffer.model.instances.render_ranges.ranges) {
Range range_range = { range.offset, range.offset + range.count };
if (range_range.intersects(buffer_range)) {
shader.set_uniform("uniform_color", range.color);
unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0;
size_t offset_bytes = static_cast<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 (has_normals)
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
buffer_range.first = buffer_range.last;
}
};
auto line_width = [](double zoom) {
return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0));
};
unsigned char begin_id = buffer_id(EMoveType::Retract);
unsigned char end_id = buffer_id(EMoveType::Count);
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":begin_id %1%, end_id %2% ")%(int)begin_id %(int)end_id;
for (unsigned char i = begin_id; i < end_id; ++i) {
TBuffer& buffer = m_buffers[i];
if (!buffer.visible || !buffer.has_data())
continue;
GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str());
if (shader != nullptr) {
shader->start_using();
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) {
shader->set_uniform("emission_factor", 0.25f);
render_as_instanced_model(buffer, *shader);
shader->set_uniform("emission_factor", 0.0f);
}
else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
shader->set_uniform("emission_factor", 0.25f);
render_as_batched_model(buffer, *shader);
shader->set_uniform("emission_factor", 0.0f);
}
else {
switch (buffer.render_primitive_type) {
case TBuffer::ERenderPrimitiveType::Point: shader_init_as_points(*shader); break;
case TBuffer::ERenderPrimitiveType::Line: shader_init_as_lines(*shader); break;
default: break;
}
int uniform_color = shader->get_uniform_location("uniform_color");
auto it_path = buffer.render_paths.begin();
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":buffer indices size %1%, render_path size %2% ")%buffer.indices.size() %buffer.render_paths.size();
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));
glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
bool has_normals = buffer.vertices.normal_size_floats() > 0;
if (has_normals) {
glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
// Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors.
switch (buffer.render_primitive_type)
{
case TBuffer::ERenderPrimitiveType::Point: {
render_as_points(it_path, buffer.render_paths.end(), *shader, uniform_color);
break;
}
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 (has_normals)
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
}
shader->stop_using();
}
}
#if ENABLE_GCODE_VIEWER_STATISTICS
auto render_sequential_range_cap = [this]
#else
auto render_sequential_range_cap = []
#endif // ENABLE_GCODE_VIEWER_STATISTICS
(const SequentialRangeCap& cap) {
GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str());
if (shader != nullptr) {
shader->start_using();
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo));
glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_bytes()));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
bool has_normals = cap.buffer->vertices.normal_size_floats() > 0;
if (has_normals) {
glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_bytes()));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
}
shader->set_uniform("uniform_color", cap.color);
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo));
glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
#if ENABLE_GCODE_VIEWER_STATISTICS
++m_statistics.gl_triangles_calls_count;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
if (has_normals)
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
shader->stop_using();
}
};
for (unsigned int i = 0; i < 2; ++i) {
if (m_sequential_range_caps[i].is_renderable())
render_sequential_range_cap(m_sequential_range_caps[i]);
}
}
void GCodeViewer::render_shells()
{
//BBS: add shell previewing logic
if ((!m_shells.previewing && !m_shells.visible) || m_shells.volumes.empty())
//if (!m_shells.visible || m_shells.volumes.empty())
return;
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
// when the background processing is enabled, it may happen that the shells data have been loaded
// before opengl has been initialized for the preview canvas.
// when this happens, the volumes' data have not been sent to gpu yet.
for (GLVolume* v : m_shells.volumes.volumes) {
if (!v->indexed_vertex_array.has_VBOs())
v->finalize_geometry(true);
}
glsafe(::glEnable(GL_DEPTH_TEST));
// glsafe(::glDepthMask(GL_FALSE));
shader->start_using();
//BBS: reopen cul faces
m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
shader->stop_using();
// glsafe(::glDepthMask(GL_TRUE));
}
//BBS: GUI refactor: add canvas size
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), 0.0f, ImGuiCond_Always, 1.0f, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f,1.0f,1.0f,0.6f));
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
ImGui::SetNextWindowBgAlpha(0.6f);
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 = 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";
auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color &color, const std::string &label,
bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array<float, 4>& offsets = { 0.0f, 0.0f, 0.0f, 0.0f },
double used_filament_m = 0.0, double used_filament_g = 0.0,
std::function<void()> callback = nullptr) {
/* BBS GUI refactor */
/*if (!visible)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
*/
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
float dummy_size = icon_size;
switch (type) {
default:
case EItemType::Rect: {
draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 5.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size + 3.0f },
ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }));
break;
}
case EItemType::Circle: {
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size + 5.0f));
draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
break;
}
case EItemType::Hexagon: {
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size + 5.0f));
draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6);
break;
}
case EItemType::Line: {
draw_list->AddLine({ pos.x + 1, pos.y + icon_size + 2 }, { pos.x + icon_size - 1, pos.y + 4 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f);
break;
case EItemType::None:
dummy_size = 0;
break;
}
}
// draw text
ImGui::Dummy({ dummy_size, dummy_size });
ImGui::SameLine();
if (callback != nullptr) {
if (ImGui::MenuItem(label.c_str()))
callback();
else {
// show tooltip
if (ImGui::IsItemHovered()) {
/* BBS GUI refactor */
/*
if (!visible)
ImGui::PopStyleVar();
*/
//ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
//ImGui::BeginTooltip();
//imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show"));
//ImGui::EndTooltip();
//ImGui::PopStyleColor();
/* BBS GUI refactor */
/*
if (!visible)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
*/
// to avoid the tooltip to change size when moving the mouse
#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
}
}
//BBS GUI:refactor
if (!time.empty()) {
ImGui::SameLine(offsets[0]);
imgui.text(time);
ImGui::SameLine(offsets[1]);
pos = ImGui::GetCursorScreenPos();
const float width = std::max(1.0f, percent_bar_size * percent / max_percent);
/* BBS GUI refactor do not draw percentage
draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f },
ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT));
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.20f, 0.64f, 1.00f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.20f, 0.64f, 1.00f, 0.00f));
ImGui::BBLProgressBar(1.0 * percent, ImVec2(25.0f, 0.0f));
ImGui::PopStyleColor(2);
ImGui::SameLine();*/
char buf[64];
::sprintf(buf, "%.1f%%", 100.0f * percent);
ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
ImGui::SameLine(offsets[3]);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0, 0.0));
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
ImGui::Checkbox("", &visible);
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(1);
} else {
if (used_filament_m > 0.0) {
char buf[64];
ImGui::SameLine(offsets[0]);
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m);
imgui.text(buf);
ImGui::SameLine(offsets[1]);
::sprintf(buf, "%.2fg", used_filament_g);
imgui.text(buf);
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0, 0.0));
ImGui::SameLine(offsets[3]);
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
ImGui::Checkbox("", &visible);
ImGui::PopStyleColor(1);
ImGui::PopStyleVar(1);
}
}
else {
imgui.text(label);
/* BBS refactor do not show used_filament info
if (used_filament_m > 0.0) {
char buf[64];
ImGui::SameLine(offsets[0]);
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m);
imgui.text(buf);
ImGui::SameLine(offsets[1]);
::sprintf(buf, "%.2f g", used_filament_g);
imgui.text(buf);
}*/
}
/* BBS GUI refactor */
/*if (!visible)
ImGui::PopStyleVar();
*/
};
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);
};
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.min + static_cast<float>(i) * step_size, decimals);
}
}
};
auto append_headers = [&imgui](const std::array<std::string, 5>& texts, const std::array<float, 4>& offsets) {
size_t i = 0;
for (; i < offsets.size(); i++) {
imgui.text(texts[i]);
ImGui::SameLine(offsets[i]);
}
imgui.text(texts[i]);
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 = [max_width](const std::vector<std::string>& labels, const std::vector<std::string>& times,
const std::array<std::string, 4>& titles, float extra_size = 0.0f) {
const ImGuiStyle& style = ImGui::GetStyle();
std::array<float, 4> ret = { 0.0f, 0.0f, 0.0f, 0.0f };
ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x;
for (size_t i = 1; i < titles.size(); i++)
ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x;
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<Color, 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))
ret.push_back({ decode_color(item.color), { previous_z, current_z } });
}
return ret;
};
auto upto_label = [](double z) {
char buf[64];
::sprintf(buf, "%.2f", z);
return _u8L("up to") + " " + std::string(buf) + " " + _u8L("mm");
};
auto above_label = [](double z) {
char buf[64];
::sprintf(buf, "%.2f", z);
return _u8L("above") + " " + std::string(buf) + " " + _u8L("mm");
};
auto fromto_label = [](double z1, double z2) {
char buf1[64];
::sprintf(buf1, "%.2f", z1);
char buf2[64];
::sprintf(buf2, "%.2f", z2);
return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm");
};
auto role_time_and_percent = [time_mode](ExtrusionRole role) {
auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair<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 used_filament_per_role = [this, imperial_units](ExtrusionRole role) {
auto it = m_print_statistics.used_filaments_per_role.find(role);
if (it == m_print_statistics.used_filaments_per_role.end())
return std::make_pair(0.0, 0.0);
double koef = imperial_units ? 1000.0 / GizmoObjectManipulation::in_to_mm : 1.0;
return std::make_pair(it->second.first * koef, it->second.second);
};
//BBS display Color Scheme
std::wstring btn_name;
if (m_fold)
btn_name = ImGui::UnfoldButtonIcon + boost::nowide::widen(std::string(""));
else
btn_name = ImGui::FoldButtonIcon + boost::nowide::widen(std::string(""));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.68f, 0.26f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.0f, 0.68f, 0.26f, 0.78f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
//ImGui::PushItemWidth(
float button_width = ImGui::CalcTextSize(into_u8(btn_name).c_str()).x;
if (ImGui::Button(into_u8(btn_name).c_str(), ImVec2(button_width, 0))) {
m_fold = !m_fold;
}
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(1);
ImGui::SameLine();
ImGui::Text(_u8L("Color Scheme").c_str());
push_combo_style();
float combo_width = ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(_u8L("Color Scheme").c_str()).x - button_width - ImGui::GetStyle().FramePadding.x * 4;
ImGui::PushItemWidth(combo_width);
ImGui::SameLine();
const char* view_type_value = view_type_items_str[m_view_type_sel].c_str();
ImGuiComboFlags flags = 0;
if (ImGui::BBLBeginCombo("", view_type_value, flags)) {
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
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();
if (m_fold) {
imgui.end();
ImGui::PopStyleColor(6);
ImGui::PopStyleVar();
return;
}
// data used to properly align items in columns when showing time
std::array<float, 4> offsets = { 0.0f, 0.0f, 0.0f, 0.0f };
std::vector<std::string> labels;
std::vector<std::string> times;
std::vector<float> percents;
std::vector<double> used_filaments_m;
std::vector<double> used_filaments_g;
float max_percent = 0.0f;
if (m_view_type == EViewType::FeatureType) {
// calculate offsets to align time/percentage data
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)) : "");
percents.push_back(percent);
max_percent = std::max(max_percent, percent);
auto [used_filament_m, used_filament_g] = used_filament_per_role(role);
used_filaments_m.push_back(used_filament_m);
used_filaments_g.push_back(used_filament_g);
}
}
std::string longest_percentage_string;
for (double item : percents) {
char buffer[64];
::sprintf(buffer, "%.2f %%", item);
if (::strlen(buffer) > longest_percentage_string.length())
longest_percentage_string = buffer;
}
if (_u8L("Percent").length() > longest_percentage_string.length())
longest_percentage_string = _u8L("Percent");
offsets = calculate_offsets(labels, times, {_u8L("Line Type"), _u8L("Time"), longest_percentage_string, _u8L("Display")}, icon_size);
}
// 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;
};
if (m_view_type == EViewType::Tool) {
// calculate used filaments data
for (size_t extruder_id : m_extruder_ids) {
if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end())
continue;
double volume = m_print_statistics.volumes_per_extruder.at(extruder_id);
auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
used_filaments_m.push_back(used_filament_m);
used_filaments_g.push_back(used_filament_g);
}
std::string longest_used_filament_string;
for (double item : used_filaments_m) {
char buffer[64];
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item);
if (::strlen(buffer) > longest_used_filament_string.length())
longest_used_filament_string = buffer;
}
offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size);
}
// extrusion paths section -> title
switch (m_view_type)
{
case EViewType::FeatureType:
{
append_headers({_u8L("Line type"), _u8L("Time"), _u8L("Percent"), "", _u8L("Display")}, offsets);
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)"));
offsets = calculate_offsets(labels, times, {_u8L("Speed (mm/s)")}, icon_size);
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::Tool:
{
append_headers({ _u8L("Filament"), _u8L("Used filament") }, offsets);
break;
}
case EViewType::ColorPrint:
{
for (size_t extruder_id : m_extruder_ids) {
if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) continue;
double volume = m_print_statistics.volumes_per_extruder.at(extruder_id);
auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
used_filaments_m.push_back(used_filament_m);
used_filaments_g.push_back(used_filament_g);
}
std::string longest_used_filament_string;
for (double item : used_filaments_m) {
char buffer[64];
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item);
if (::strlen(buffer) > longest_used_filament_string.length()) longest_used_filament_string = buffer;
}
std::string longest_used_filament_g_string;
for (double item : used_filaments_g) {
char buffer[64];
::sprintf(buffer, imperial_units ? "%.2fg" : "%.2fg", item);
if (::strlen(buffer) > longest_used_filament_g_string.length()) longest_used_filament_g_string = buffer;
}
// BBL XX is placeholder
offsets = calculate_offsets(labels, times, {_u8L("Filament N XX"), longest_used_filament_string, longest_used_filament_g_string, _u8L("Display")}, icon_size);
append_headers({ _u8L("Color Print"), _u8L("Comsumption"), "", "", _u8L("Display") }, offsets);
break;
}
default: { break; }
}
auto append_option_item = [this,append_item](EMoveType type, std::array<float, 4> offsets) {
auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const Color& color, const std::string& label, bool visible) {
append_item(EItemType::Rect, color, label, visible, "", 0.0f, 0.0f, offsets, 0.0, 0.0, [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) {
//TODO display travel time, salt.wei
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);
append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast<unsigned int>(role)], labels[i],
visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [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) {
append_option_item(item,offsets);
}
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();
append_headers({_u8L("Options"), "", "", "", _u8L("Display")}, offsets);
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"), travel_visible, "", 0.0f, 0.0f, offsets, 0.0, 0.0, [this, travel_visible]() {
m_buffers[buffer_id(EMoveType::Travel)].visible = !m_buffers[buffer_id(EMoveType::Travel)].visible;
// update buffers' render paths
refresh_render_paths(false, false);
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::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 2); break; }
case EViewType::Tool:
{
// shows only extruders actually used
size_t i = 0;
for (unsigned char extruder_id : m_extruder_ids) {
append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1),
true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]);
i++;
}
break;
}
case EViewType::ColorPrint:
{
const std::vector<CustomGCode::Item>& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z;
size_t total_items = 1;
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);
if (m_extruders_count == 1) { // single extruder use case
const std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(0, custom_gcode_per_print_z);
const int items_cnt = static_cast<int>(cp_values.size());
if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode
append_item(EItemType::Rect, m_tools.m_tool_colors.front(), _u8L("Filament 1"));
}
else {
for (int i = items_cnt; i >= 0; --i) {
// create label for color change item
if (i == 0) {
append_item(EItemType::Rect, m_tools.m_tool_colors[0], upto_label(cp_values.front().second.first));
break;
}
else if (i == items_cnt) {
append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second));
continue;
}
append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first));
}
}
}
else { // multi extruder use case
// shows only extruders actually used
int extruder_idx = 0;
for (unsigned char i : m_extruder_ids) {
const std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(i, custom_gcode_per_print_z);
const int items_cnt = static_cast<int>(cp_values.size());
if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode
const bool filament_visible = m_tools.m_tool_visibles[i];
if (extruder_idx < used_filaments_m.size() && extruder_idx < used_filaments_g.size()) {
append_item(EItemType::Rect, m_tools.m_tool_colors[i], _u8L("Filament") + " " + std::to_string(i + 1), filament_visible, "", 0.0f, 0.0f, offsets,
used_filaments_m[extruder_idx], used_filaments_g[extruder_idx], [this, i]() {
m_tools.m_tool_visibles[i] = !m_tools.m_tool_visibles[i];
// update buffers' render paths
refresh_render_paths(false, false);
update_moves_slider();
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
});
}
}
else {
for (int j = items_cnt; j >= 0; --j) {
// create label for color change item
std::string label = _u8L("Filament") + " " + std::to_string(i + 1);
if (j == 0) {
label += " " + upto_label(cp_values.front().second.first);
append_item(EItemType::Rect, m_tools.m_tool_colors[i], label);
break;
}
else if (j == items_cnt) {
label += " " + above_label(cp_values[j - 1].second.second);
append_item(EItemType::Rect, cp_values[j - 1].first, label);
continue;
}
label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first);
append_item(EItemType::Rect, cp_values[j - 1].first, label);
}
}
extruder_idx++;
}
}
if (need_scrollable)
ImGui::EndChild();
for (auto item : options_items)
append_option_item(item,offsets);
//BBS display filament change times
if (m_print_statistics.total_filamentchanges > 0) {
//BBS: calculate total flushed filaments data
double total_flushed_filament_m = 0.0;
double total_flushed_filament_g = 0.0;
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())
continue;
double volume = m_print_statistics.flush_per_filament.at(extruder_id);
auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
total_flushed_filament_m += used_filament_m;
total_flushed_filament_g += used_filament_g;
}
std::string flushed_filament_str = _u8L("Flushed filament");
std::string filament_change_str = _u8L("Filament change times");
float max_len = 10.0f + ImGui::GetStyle().ItemSpacing.x;
max_len += std::max(ImGui::CalcTextSize(filament_change_str.c_str()).x, ImGui::CalcTextSize(flushed_filament_str.c_str()).x);
//BBS: display total flushed filament
{
imgui.text(flushed_filament_str + ":");
ImGui::SameLine(max_len);
char buf[64];
::sprintf(buf, "%.2f m", total_flushed_filament_m);
imgui.text(buf);
ImGui::SameLine();
::sprintf(buf, " %.2f g", total_flushed_filament_g);
imgui.text(buf);
}
//BBS display filament change times
{
imgui.text(filament_change_str + ":");
ImGui::SameLine(max_len);
char temp_buf[64];
::sprintf(temp_buf, " %d", m_print_statistics.total_filamentchanges);
imgui.text(temp_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;
Color color1;
Color 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;
std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z;
// BBS
int extruders_count = wxGetApp().filaments_cnt();
std::vector<Color> last_color(extruders_count);
for (int i = 0; i < extruders_count; ++i) {
last_color[i] = m_tools.m_tool_colors[i];
}
int last_extruder_id = 1;
int color_change_idx = 0;
for (const auto& time_rec : times) {
switch (time_rec.first)
{
case CustomGCode::PausePrint: {
auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
if (it != custom_gcode_per_print_z.end()) {
items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second });
items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second });
custom_gcode_per_print_z.erase(it);
}
break;
}
case CustomGCode::ColorChange: {
auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
if (it != custom_gcode_per_print_z.end()) {
items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) });
items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second });
last_color[it->extruder - 1] = decode_color(it->color);
last_extruder_id = it->extruder;
custom_gcode_per_print_z.erase(it);
}
else
items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) });
break;
}
default: { break; }
}
}
return items;
};
auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array<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 },
ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }));
pos.x += icon_size;
draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }));
ImGui::SameLine(offsets[0]);
imgui.text(short_time(get_time_dhms(times.second - times.first)));
};
auto append_print = [&imgui, imperial_units](const Color& color, const std::array<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 },
ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }));
ImGui::SameLine(offsets[0]);
imgui.text(short_time(get_time_dhms(times.second)));
ImGui::SameLine(offsets[1]);
imgui.text(short_time(get_time_dhms(times.first)));
if (used_filament.first > 0.0f) {
char buffer[64];
ImGui::SameLine(offsets[2]);
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first);
imgui.text(buffer);
ImGui::SameLine(offsets[3]);
::sprintf(buffer, "%.2f g", used_filament.second);
imgui.text(buffer);
}
};
PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change);
if (!partial_times.empty()) {
labels.clear();
times.clear();
for (const PartialTime& item : partial_times) {
switch (item.type)
{
case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; }
case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; }
case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; }
}
times.push_back(short_time(get_time_dhms(item.times.second)));
}
std::string longest_used_filament_string;
for (const PartialTime& item : partial_times) {
if (item.used_filament.first > 0.0f) {
char buffer[64];
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first);
if (::strlen(buffer) > longest_used_filament_string.length())
longest_used_filament_string = buffer;
}
}
//offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size);
//ImGui::Spacing();
//append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets);
//const bool need_scrollable = static_cast<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.title(_u8L("Wipe"));
// items
append_item(EItemType::Line, Wipe_Color, _u8L("Wipe"));
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]);
}
}
}
}
// total estimated printing time section
if (show_estimated_time) {
ImGui::Spacing();
std::string time_title = _u8L("Total 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.title(time_title);
std::string filament_str = _u8L("Filament");
std::string prepare_str = _u8L("Prepare time");
std::string print_str = _u8L("Model printing time");
std::string total_str = _u8L("Total");
float max_len = 10.0f + ImGui::GetStyle().ItemSpacing.x;
if (time_mode.layers_times.empty())
max_len += ImGui::CalcTextSize(total_str.c_str()).x;
else {
max_len += 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), ImGui::CalcTextSize(filament_str.c_str()).x));
}
//BBS display filament cost
imgui.text(filament_str + ":");
ImGui::SameLine(max_len);
//BBS: use current plater's print statistics
const PrintStatistics& ps = wxGetApp().plater()->get_partplate_list().get_current_fff_print().print_statistics();
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
char buf[64];
double koef = imperial_units ? GizmoObjectManipulation::in_to_mm : 1000.0;
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / /*1000*/koef);
imgui.text(buf);
ImGui::SameLine();
double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1;
::sprintf(buf, imperial_units ?" %.2f oz":" %.2f g", ps.total_weight / unit_conver);
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 prepeare time
if (role_time(erCustom) != 0.0f) {
imgui.text(prepare_str + ":");
ImGui::SameLine(max_len);
imgui.text(short_time(get_time_dhms(role_time(erCustom))));
}
imgui.text(print_str + ":");
ImGui::SameLine(max_len);
imgui.text(short_time(get_time_dhms(time_mode.time - role_time(erCustom))));
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; }
}
}
legend_height = ImGui::GetCurrentWindow()->Size.y;
imgui.end();
ImGui::PopStyleColor(6);
ImGui::PopStyleVar();
}
void GCodeViewer::push_combo_style()
{
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.0f, 0.0f, 0.0f, 0.8f));
ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
}
void GCodeViewer::pop_combo_style()
{
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(8);
}
void GCodeViewer::render_slider(int canvas_width, int canvas_height) {
m_moves_slider->render(canvas_width, canvas_height);
m_layers_slider->render(canvas_width, canvas_height);
}
#if ENABLE_GCODE_VIEWER_STATISTICS
void GCodeViewer::render_statistics()
{
static const float offset = 275.0f;
ImGuiWrapper& imgui = *wxGetApp().imgui();
auto add_time = [this, &imgui](const std::string& label, int64_t time) {
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
ImGui::SameLine(offset);
imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast<float>(time) * 0.001f) + ")");
};
auto add_memory = [this, &imgui](const std::string& label, int64_t memory) {
auto format_string = [memory](const std::string& units, float value) {
return std::to_string(memory) + " bytes (" +
Slic3r::float_to_string_decimal_point(float(memory) * value, 3)
+ " " + units + ")";
};
static const float kb = 1024.0f;
static const float inv_kb = 1.0f / kb;
static const float mb = 1024.0f * kb;
static const float inv_mb = 1.0f / mb;
static const float gb = 1024.0f * mb;
static const float inv_gb = 1.0f / gb;
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
ImGui::SameLine(offset);
if (static_cast<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 = [this, &imgui](const std::string& label, int64_t counter) {
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
ImGui::SameLine(offset);
imgui.text(std::to_string(counter));
};
imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f);
ImGui::SetNextWindowSizeConstraints({ 300.0f, 100.0f }, { 600.0f, 900.0f });
imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
if (ImGui::CollapsingHeader("Time")) {
add_time(std::string("GCodeProcessor:"), m_statistics.results_time);
ImGui::Separator();
add_time(std::string("Load:"), m_statistics.load_time);
add_time(std::string(" Load vertices:"), m_statistics.load_vertices);
add_time(std::string(" Smooth vertices:"), m_statistics.smooth_vertices);
add_time(std::string(" Load indices:"), m_statistics.load_indices);
add_time(std::string("Refresh:"), m_statistics.refresh_time);
add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time);
}
if (ImGui::CollapsingHeader("OpenGL calls")) {
add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count);
add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count);
add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count);
add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count);
ImGui::Separator();
add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count);
add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count);
}
if (ImGui::CollapsingHeader("CPU memory")) {
add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size);
ImGui::Separator();
add_memory(std::string("Paths:"), m_statistics.paths_size);
add_memory(std::string("Render paths:"), m_statistics.render_paths_size);
add_memory(std::string("Models instances:"), m_statistics.models_instances_size);
}
if (ImGui::CollapsingHeader("GPU memory")) {
add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size);
add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size);
add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size);
ImGui::Separator();
add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size);
add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size);
}
if (ImGui::CollapsingHeader("Other")) {
add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count);
add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count);
add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count);
add_counter(std::string("Instances count:"), m_statistics.instances_count);
add_counter(std::string("Batched count:"), m_statistics.batched_count);
ImGui::Separator();
add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count);
add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count);
}
imgui.end();
}
#endif // ENABLE_GCODE_VIEWER_STATISTICS
void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) const
{
if (Slic3r::get_logging_level() >= 5) {
int64_t paths_size = 0;
int64_t render_paths_size = 0;
for (const TBuffer& buffer : m_buffers) {
paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path);
render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath);
for (const RenderPath& path : buffer.render_paths) {
render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int);
render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t);
}
}
int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double);
layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format("paths_size %1%, render_paths_size %2%,layers_size %3%, additional %4%\n")
%paths_size %render_paths_size %layers_size %additional;
BOOST_LOG_TRIVIAL(trace) << label
<< "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");"
<< log_memory_info();
}
}
GCodeViewer::Color GCodeViewer::option_color(EMoveType move_type) const
{
switch (move_type)
{
case EMoveType::Tool_change: { return Options_Colors[static_cast<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