6032 lines
287 KiB
C++
6032 lines
287 KiB
C++
#include "libslic3r/libslic3r.h"
|
|
#include "GCodeViewer.hpp"
|
|
|
|
#include "libslic3r/BuildVolume.hpp"
|
|
#include "libslic3r/ClipperUtils.hpp"
|
|
#include "libslic3r/Print.hpp"
|
|
#include "libslic3r/Geometry.hpp"
|
|
#include "libslic3r/Model.hpp"
|
|
#include "libslic3r/Utils.hpp"
|
|
#include "libslic3r/LocalesUtils.hpp"
|
|
#include "libslic3r/PresetBundle.hpp"
|
|
//BBS: add convex hull logic for toolpath check
|
|
#include "libslic3r/Geometry/ConvexHull.hpp"
|
|
|
|
#include "GUI_App.hpp"
|
|
#include "MainFrame.hpp"
|
|
#include "Plater.hpp"
|
|
#include "Camera.hpp"
|
|
#include "I18N.hpp"
|
|
#include "GUI_Utils.hpp"
|
|
#include "GUI.hpp"
|
|
#include "GLCanvas3D.hpp"
|
|
#include "GLToolbar.hpp"
|
|
#include "GUI_Preview.hpp"
|
|
#include "libslic3r/Print.hpp"
|
|
#include "libslic3r/Layer.hpp"
|
|
#include "Widgets/ProgressDialog.hpp"
|
|
|
|
#include <imgui/imgui_internal.h>
|
|
|
|
#include <GL/glew.h>
|
|
#include <boost/log/trivial.hpp>
|
|
#include <boost/algorithm/string/split.hpp>
|
|
#include <boost/nowide/cstdio.hpp>
|
|
#include <boost/nowide/fstream.hpp>
|
|
#include <wx/progdlg.h>
|
|
#include <wx/numformatter.h>
|
|
|
|
#include <array>
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
|
|
namespace Slic3r {
|
|
namespace GUI {
|
|
|
|
//BBS translation of EViewType
|
|
//const std::string EViewType_Map[(int) GCodeViewer::EViewType::Count] = {
|
|
// _u8L("Line Type"),
|
|
// _u8L("Layer Height"),
|
|
// _u8L("Line Width"),
|
|
// _u8L("Speed"),
|
|
// _u8L("Fan Speed"),
|
|
// _u8L("Temperature"),
|
|
// _u8L("Flow"),
|
|
// _u8L("Tool"),
|
|
// _u8L("Filament")
|
|
// };
|
|
|
|
static std::string get_view_type_string(GCodeViewer::EViewType view_type)
|
|
{
|
|
if (view_type == GCodeViewer::EViewType::FeatureType)
|
|
return _u8L("Line Type");
|
|
else if (view_type == GCodeViewer::EViewType::Height)
|
|
return _u8L("Layer Height");
|
|
else if (view_type == GCodeViewer::EViewType::Width)
|
|
return _u8L("Line Width");
|
|
else if (view_type == GCodeViewer::EViewType::Feedrate)
|
|
return _u8L("Speed");
|
|
else if (view_type == GCodeViewer::EViewType::FanSpeed)
|
|
return _u8L("Fan Speed");
|
|
else if (view_type == GCodeViewer::EViewType::Temperature)
|
|
return _u8L("Temperature");
|
|
else if (view_type == GCodeViewer::EViewType::VolumetricRate)
|
|
return _u8L("Flow");
|
|
else if (view_type == GCodeViewer::EViewType::Tool)
|
|
return _u8L("Tool");
|
|
else if (view_type == GCodeViewer::EViewType::ColorPrint)
|
|
return _u8L("Filament");
|
|
else if (view_type == GCodeViewer::EViewType::LayerTime)
|
|
return _u8L("Layer Time");
|
|
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;
|
|
}
|
|
}
|
|
else if (color.size() == 9 && color.front() == '#') {
|
|
for (size_t j = 0; j < 4; ++j) {
|
|
int digit1 = hex_digit_to_int(*c++);
|
|
int digit2 = hex_digit_to_int(*c++);
|
|
if (digit1 == -1 || digit2 == -1)
|
|
break;
|
|
|
|
ret[j] = float(digit1 * 16 + digit2) * INV_255;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static std::vector<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();
|
|
s_ids.shrink_to_fit();
|
|
buffer.clear();
|
|
buffer.shrink_to_fit();
|
|
render_ranges.reset();
|
|
}
|
|
|
|
void GCodeViewer::IBuffer::reset()
|
|
{
|
|
// release gpu memory
|
|
if (ibo > 0) {
|
|
glsafe(::glDeleteBuffers(1, &ibo));
|
|
ibo = 0;
|
|
}
|
|
|
|
vbo = 0;
|
|
count = 0;
|
|
}
|
|
|
|
bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const
|
|
{
|
|
auto matches_percent = [](float value1, float value2, float max_percent) {
|
|
return std::abs(value2 - value1) / value1 <= max_percent;
|
|
};
|
|
|
|
switch (move.type)
|
|
{
|
|
case EMoveType::Tool_change:
|
|
case EMoveType::Color_change:
|
|
case EMoveType::Pause_Print:
|
|
case EMoveType::Custom_GCode:
|
|
case EMoveType::Retract:
|
|
case EMoveType::Unretract:
|
|
case EMoveType::Seam:
|
|
case EMoveType::Extrude: {
|
|
// use rounding to reduce the number of generated paths
|
|
return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role &&
|
|
move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed &&
|
|
height == round_to_bin(move.height) && width == round_to_bin(move.width) &&
|
|
matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f) && layer_time == move.layer_duration;
|
|
}
|
|
case EMoveType::Travel: {
|
|
return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id;
|
|
}
|
|
default: { return false; }
|
|
}
|
|
}
|
|
|
|
void GCodeViewer::TBuffer::Model::reset()
|
|
{
|
|
instances.reset();
|
|
}
|
|
|
|
void GCodeViewer::TBuffer::reset()
|
|
{
|
|
vertices.reset();
|
|
for (IBuffer& buffer : indices) {
|
|
buffer.reset();
|
|
}
|
|
|
|
indices.clear();
|
|
paths.clear();
|
|
render_paths.clear();
|
|
model.reset();
|
|
}
|
|
|
|
void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id)
|
|
{
|
|
Path::Endpoint endpoint = { b_id, i_id, s_id, move.position };
|
|
// use rounding to reduce the number of generated paths
|
|
paths.push_back({ move.type, move.extrusion_role, move.delta_extruder,
|
|
round_to_bin(move.height), round_to_bin(move.width),
|
|
move.feedrate, move.fan_speed, move.temperature,
|
|
move.volumetric_rate(), move.layer_duration, move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } });
|
|
}
|
|
|
|
GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const
|
|
{
|
|
float global_t = 0.0f;
|
|
const float step = step_size(type);
|
|
if (step > 0.0f) {
|
|
switch (type) {
|
|
default:
|
|
case EType::Linear: {
|
|
global_t = (value > min) ? (value - min) / step : 0.0f;
|
|
break;
|
|
}
|
|
case EType::Logarithmic: {
|
|
global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const size_t color_max_idx = Range_Colors.size() - 1;
|
|
|
|
// Compute the two colors just below (low) and above (high) the input value
|
|
const size_t color_low_idx = std::clamp<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
|
|
auto color = lerp(ColorRGBA(Range_Colors[color_low_idx]), ColorRGBA(Range_Colors[color_high_idx]), local_t);
|
|
return color.get_data();
|
|
}
|
|
|
|
float GCodeViewer::Extrusions::Range::step_size(EType type) const {
|
|
switch (type) {
|
|
default:
|
|
case EType::Linear: {
|
|
return (max > min) ? (max - min) / (static_cast<float>(Range_Colors.size()) - 1.0f) : 0.0f;
|
|
}
|
|
case EType::Logarithmic: {
|
|
return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast<float>(Range_Colors.size()) - 1.0f) : 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
float GCodeViewer::Extrusions::Range::get_value_at_step(int step) const {
|
|
if (!log_scale)
|
|
return min + static_cast<float>(step) * step_size();
|
|
else
|
|
// calculate log-average
|
|
{
|
|
float min_range = min;
|
|
if (min_range == 0)
|
|
min_range = 0.0001f;
|
|
float step_size = std::log(max / min_range) / (static_cast<float>(Range_Colors.size()) - 1.0f);
|
|
return std::exp(std::log(min) + static_cast<float>(step) * step_size);
|
|
}
|
|
}
|
|
GCodeViewer::SequentialRangeCap::~SequentialRangeCap() {
|
|
if (ibo > 0)
|
|
glsafe(::glDeleteBuffers(1, &ibo));
|
|
}
|
|
|
|
void GCodeViewer::SequentialRangeCap::reset() {
|
|
if (ibo > 0)
|
|
glsafe(::glDeleteBuffers(1, &ibo));
|
|
|
|
buffer = nullptr;
|
|
ibo = 0;
|
|
vbo = 0;
|
|
color = { 0.0f, 0.0f, 0.0f, 1.0f };
|
|
}
|
|
|
|
void GCodeViewer::SequentialView::Marker::init(std::string filename)
|
|
{
|
|
if (filename.empty()) {
|
|
m_model.init_from(stilized_arrow(16, 1.5f, 3.0f, 0.8f, 3.0f));
|
|
} else {
|
|
m_model.init_from_file(filename);
|
|
}
|
|
m_model.set_color(-1, {1.0f, 1.0f, 1.0f, 0.5f});
|
|
}
|
|
|
|
void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position)
|
|
{
|
|
m_world_position = position;
|
|
m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast<double>()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast<float>();
|
|
}
|
|
|
|
void GCodeViewer::SequentialView::Marker::update_curr_move(const GCodeProcessorResult::MoveVertex move) {
|
|
m_curr_move = move;
|
|
}
|
|
|
|
//BBS: GUI refactor: add canvas size from parameters
|
|
void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_height, const EViewType& view_type) const
|
|
{
|
|
if (!m_visible)
|
|
return;
|
|
|
|
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
glsafe(::glEnable(GL_BLEND));
|
|
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
|
|
|
shader->start_using();
|
|
shader->set_uniform("emission_factor", 0.0f);
|
|
|
|
glsafe(::glPushMatrix());
|
|
glsafe(::glMultMatrixf(m_world_transform.data()));
|
|
|
|
m_model.render();
|
|
|
|
glsafe(::glPopMatrix());
|
|
|
|
shader->stop_using();
|
|
|
|
glsafe(::glDisable(GL_BLEND));
|
|
|
|
static float last_window_width = 0.0f;
|
|
size_t text_line = 0;
|
|
static size_t last_text_line = 0;
|
|
const ImU32 text_name_clr = m_is_dark ? IM_COL32(255, 255, 255, 0.88 * 255) : IM_COL32(38, 46, 48, 255);
|
|
const ImU32 text_value_clr = m_is_dark ? IM_COL32(255, 255, 255, 0.4 * 255) : IM_COL32(144, 144, 144, 255);
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
//BBS: GUI refactor: add canvas size from parameters
|
|
imgui.set_next_window_pos(0.5f * static_cast<float>(canvas_width), static_cast<float>(canvas_height), ImGuiCond_Always, 0.5f, 1.0f);
|
|
imgui.push_toolbar_style(m_scale);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0, 4.0 * m_scale));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0 * m_scale, 6.0 * m_scale));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, text_name_clr);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, text_value_clr);
|
|
imgui.begin(std::string("ExtruderPosition"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar);
|
|
ImGui::AlignTextToFramePadding();
|
|
//BBS: minus the plate offset when show tool position
|
|
PartPlateList& partplate_list = wxGetApp().plater()->get_partplate_list();
|
|
PartPlate* plate = partplate_list.get_curr_plate();
|
|
const Vec3f position = m_world_position + m_world_offset;
|
|
std::string x = ImGui::ColorMarkerStart + std::string("X: ") + ImGui::ColorMarkerEnd;
|
|
std::string y = ImGui::ColorMarkerStart + std::string("Y: ") + ImGui::ColorMarkerEnd;
|
|
std::string z = ImGui::ColorMarkerStart + std::string("Z: ") + ImGui::ColorMarkerEnd;
|
|
std::string height = ImGui::ColorMarkerStart + _u8L("Height: ") + ImGui::ColorMarkerEnd;
|
|
std::string width = ImGui::ColorMarkerStart + _u8L("Width: ") + ImGui::ColorMarkerEnd;
|
|
std::string speed = ImGui::ColorMarkerStart + _u8L("Speed: ") + ImGui::ColorMarkerEnd;
|
|
std::string flow = ImGui::ColorMarkerStart + _u8L("Flow: ") + ImGui::ColorMarkerEnd;
|
|
std::string layer_time = ImGui::ColorMarkerStart + _u8L("Layer Time: ") + ImGui::ColorMarkerEnd;
|
|
std::string fanspeed = ImGui::ColorMarkerStart + _u8L("Fan Speed: ") + ImGui::ColorMarkerEnd;
|
|
std::string temperature = ImGui::ColorMarkerStart + _u8L("Temperature: ") + ImGui::ColorMarkerEnd;
|
|
const float item_size = imgui.calc_text_size("X: 000.000 ").x;
|
|
const float item_spacing = imgui.get_item_spacing().x;
|
|
const float window_padding = ImGui::GetStyle().WindowPadding.x;
|
|
|
|
char buf[1024];
|
|
// extra text depends on whether current move is extrude type
|
|
bool show_extra_text = m_curr_move.type == EMoveType::Extrude;
|
|
// FeatureType and ColorPrint shall only show x,y,z
|
|
if (view_type == EViewType::FeatureType || view_type == EViewType::ColorPrint)
|
|
show_extra_text = false;
|
|
// Feedrate and LayerTime shall always show extra text
|
|
else if (view_type == EViewType::Feedrate || view_type == EViewType::LayerTime)
|
|
show_extra_text = true;
|
|
if (show_extra_text)
|
|
{
|
|
sprintf(buf, "%s%.3f", x.c_str(), position.x() - plate->get_origin().x());
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
|
|
ImGui::SameLine(window_padding + item_size + item_spacing);
|
|
sprintf(buf, "%s%.3f", y.c_str(), position.y() - plate->get_origin().y());
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
|
|
sprintf(buf, "%s%.3f", z.c_str(), position.z());
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
|
|
switch (view_type) {
|
|
case EViewType::Height: {
|
|
ImGui::SameLine(window_padding + item_size + item_spacing);
|
|
sprintf(buf, "%s%.2f", height.c_str(), m_curr_move.height);
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
break;
|
|
}
|
|
case EViewType::Width: {
|
|
ImGui::SameLine(window_padding + item_size + item_spacing);
|
|
sprintf(buf, "%s%.2f", width.c_str(), m_curr_move.width);
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
break;
|
|
}
|
|
case EViewType::Feedrate: {
|
|
ImGui::SameLine(window_padding + item_size + item_spacing);
|
|
sprintf(buf, "%s%.0f", speed.c_str(), m_curr_move.feedrate);
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
break;
|
|
}
|
|
case EViewType::VolumetricRate: {
|
|
ImGui::SameLine(window_padding + item_size + item_spacing);
|
|
sprintf(buf, "%s%.2f", flow.c_str(), m_curr_move.volumetric_rate());
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
break;
|
|
}
|
|
case EViewType::FanSpeed: {
|
|
ImGui::SameLine(window_padding + item_size + item_spacing);
|
|
sprintf(buf, "%s%.0f", fanspeed.c_str(), m_curr_move.fan_speed);
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
break;
|
|
}
|
|
case EViewType::Temperature: {
|
|
ImGui::SameLine(window_padding + item_size + item_spacing);
|
|
sprintf(buf, "%s%.0f", temperature.c_str(), m_curr_move.temperature);
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
break;
|
|
}
|
|
case EViewType::LayerTime:{
|
|
ImGui::SameLine(window_padding + item_size + item_spacing);
|
|
sprintf(buf, "%s%.1f", layer_time.c_str(), m_curr_move.layer_duration);
|
|
ImGui::PushItemWidth(item_size);
|
|
imgui.text(buf);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
text_line = 2;
|
|
}
|
|
else {
|
|
sprintf(buf, "%s%.3f", x.c_str(), position.x() - plate->get_origin().x());
|
|
imgui.text(buf);
|
|
|
|
ImGui::SameLine();
|
|
sprintf(buf, "%s%.3f", y.c_str(), position.y() - plate->get_origin().y());
|
|
imgui.text(buf);
|
|
|
|
ImGui::SameLine();
|
|
sprintf(buf, "%s%.3f", z.c_str(), position.z());
|
|
imgui.text(buf);
|
|
|
|
text_line = 1;
|
|
}
|
|
|
|
// force extra frame to automatically update window size
|
|
float window_width = ImGui::GetWindowWidth();
|
|
if (window_width != last_window_width || text_line != last_text_line) {
|
|
last_window_width = window_width;
|
|
last_text_line = text_line;
|
|
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
imgui.set_requires_extra_frame();
|
|
#else
|
|
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
|
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
|
|
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
}
|
|
|
|
imgui.end();
|
|
ImGui::PopStyleVar(2);
|
|
ImGui::PopStyleColor(2);
|
|
imgui.pop_toolbar_style();
|
|
}
|
|
|
|
void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, const std::vector<size_t> &lines_ends)
|
|
{
|
|
assert(! m_file.is_open());
|
|
if (m_file.is_open())
|
|
return;
|
|
|
|
m_filename = filename;
|
|
m_lines_ends = lines_ends;
|
|
|
|
m_selected_line_id = 0;
|
|
m_last_lines_size = 0;
|
|
|
|
try
|
|
{
|
|
m_file.open(boost::filesystem::path(m_filename));
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": mapping file " << m_filename;
|
|
}
|
|
catch (...)
|
|
{
|
|
BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window.";
|
|
reset();
|
|
}
|
|
}
|
|
|
|
//BBS: GUI refactor: move to right
|
|
void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, float right, uint64_t curr_line_id) const
|
|
//void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const
|
|
{
|
|
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 = { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f };
|
|
static const ImVec4 SELECTION_RECT_COLOR = { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f };
|
|
static const ImVec4 COMMAND_COLOR = m_is_dark ? ImVec4( 240.0f / 255.0f, 240.0f / 255.0f, 240.0f / 255.0f, 1.0f ) : ImVec4( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
static const ImVec4 PARAMETERS_COLOR = m_is_dark ? ImVec4( 179.0f / 255.0f, 179.0f / 255.0f, 179.0f / 255.0f, 1.0f ) : ImVec4( 206.0f / 255.0f, 206.0f / 255.0f, 206.0f / 255.0f, 1.0f );
|
|
static const ImVec4 COMMENT_COLOR = m_is_dark ? ImVec4(129.0f / 255.0f, 129.0f / 255.0f, 129.0f / 255.0f, 1.0f) : ImVec4( 172.0f / 255.0f, 172.0f / 255.0f, 172.0f / 255.0f, 1.0f );
|
|
|
|
if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0)
|
|
return;
|
|
|
|
// window height
|
|
const float wnd_height = bottom - top;
|
|
|
|
// number of visible lines
|
|
const float text_height = ImGui::CalcTextSize("0").y;
|
|
const ImGuiStyle& style = ImGui::GetStyle();
|
|
const uint64_t lines_count = static_cast<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.8f);
|
|
imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
|
|
|
|
// center the text in the window by pushing down the first line
|
|
const float f_lines_count = static_cast<float>(lines_count);
|
|
ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y));
|
|
|
|
// render text lines
|
|
for (uint64_t id = start_id; id <= end_id; ++id) {
|
|
const Line& line = m_lines[id - start_id];
|
|
|
|
// rect around the current selected line
|
|
if (id == curr_line_id) {
|
|
//BBS: GUI refactor: move to right
|
|
const float pos_y = ImGui::GetCursorScreenPos().y;
|
|
const float pos_x = ImGui::GetCursorScreenPos().x;
|
|
const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y;
|
|
const float half_ItemSpacing_x = 0.5f * style.ItemSpacing.x;
|
|
//ImGui::GetWindowDrawList()->AddRect({ half_padding_x, pos_y - half_ItemSpacing_y },
|
|
// { ImGui::GetCurrentWindow()->Size.x - half_padding_x, pos_y + text_height + half_ItemSpacing_y },
|
|
// ImGui::GetColorU32(SELECTION_RECT_COLOR));
|
|
ImGui::GetWindowDrawList()->AddRect({ pos_x - half_ItemSpacing_x, pos_y - half_ItemSpacing_y },
|
|
{ right - half_ItemSpacing_x, pos_y + text_height + half_ItemSpacing_y },
|
|
ImGui::GetColorU32(SELECTION_RECT_COLOR));
|
|
}
|
|
|
|
// render line number
|
|
const std::string id_str = std::to_string(id);
|
|
// spacer to right align text
|
|
ImGui::Dummy({ id_width - ImGui::CalcTextSize(id_str.c_str()).x, text_height });
|
|
ImGui::SameLine(0.0f, 0.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, LINE_NUMBER_COLOR);
|
|
imgui.text(id_str);
|
|
ImGui::PopStyleColor();
|
|
|
|
if (!line.command.empty() || !line.comment.empty())
|
|
ImGui::SameLine();
|
|
|
|
// render command
|
|
if (!line.command.empty()) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, COMMAND_COLOR);
|
|
imgui.text(line.command);
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
// render parameters
|
|
if (!line.parameters.empty()) {
|
|
ImGui::SameLine(0.0f, 0.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, PARAMETERS_COLOR);
|
|
imgui.text(line.parameters);
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
// render comment
|
|
if (!line.comment.empty()) {
|
|
if (!line.command.empty())
|
|
ImGui::SameLine(0.0f, 0.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, COMMENT_COLOR);
|
|
imgui.text(line.comment);
|
|
ImGui::PopStyleColor();
|
|
}
|
|
}
|
|
|
|
imgui.end();
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file()
|
|
{
|
|
//BBS: add log to trace the gcode file issue
|
|
if (m_file.is_open()) {
|
|
m_file.close();
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished mapping file " << m_filename;
|
|
}
|
|
}
|
|
//BBS: GUI refactor: move to the right
|
|
void GCodeViewer::SequentialView::render(float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) const
|
|
{
|
|
marker.render(canvas_width, canvas_height, view_type);
|
|
|
|
//float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height();
|
|
// BBS
|
|
#if 0
|
|
if (wxGetApp().is_editor())
|
|
bottom -= wxGetApp().plater()->get_view_toolbar().get_height();
|
|
#endif
|
|
//gcode_window.render(legend_height, bottom, static_cast<uint64_t>(gcode_ids[current.last]));
|
|
gcode_window.render(legend_height, (float)canvas_height, (float)canvas_width - (float)right_margin, 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 {
|
|
if(type == EMoveType::Seam)
|
|
buffer.visible = true;
|
|
|
|
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel;
|
|
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
|
|
buffer.shader = "gouraud_light";
|
|
|
|
buffer.model.data = diamond(16);
|
|
buffer.model.color = option_color(type);
|
|
buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel;
|
|
// }
|
|
break;
|
|
}
|
|
case EMoveType::Wipe:
|
|
case EMoveType::Extrude: {
|
|
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
|
|
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
|
|
buffer.shader = "gouraud_light";
|
|
break;
|
|
}
|
|
case EMoveType::Travel: {
|
|
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line;
|
|
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
|
|
buffer.shader = "toolpaths_lines";
|
|
break;
|
|
}
|
|
}
|
|
|
|
set_toolpath_move_type_visible(EMoveType::Extrude, true);
|
|
}
|
|
|
|
// initializes tool marker
|
|
std::string filename;
|
|
if (preset_bundle != nullptr) {
|
|
const Preset* curr = &preset_bundle->printers.get_selected_preset();
|
|
if (curr->is_system)
|
|
filename = PresetUtils::system_printer_hotend_model(*curr);
|
|
else {
|
|
auto *printer_model = curr->config.opt<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::on_change_color_mode(bool is_dark) {
|
|
m_is_dark = is_dark;
|
|
m_sequential_view.marker.on_change_color_mode(m_is_dark);
|
|
m_sequential_view.gcode_window.on_change_color_mode(m_is_dark);
|
|
}
|
|
|
|
void GCodeViewer::set_scale(float scale)
|
|
{
|
|
if(m_scale != scale)m_scale = scale;
|
|
if (m_sequential_view.m_scale != scale) {
|
|
m_sequential_view.m_scale = scale;
|
|
m_sequential_view.marker.m_scale = scale;
|
|
}
|
|
}
|
|
|
|
void GCodeViewer::update_by_mode(ConfigOptionMode mode)
|
|
{
|
|
view_type_items.clear();
|
|
view_type_items_str.clear();
|
|
options_items.clear();
|
|
|
|
// BBS initialzed view_type items
|
|
view_type_items.push_back(EViewType::FeatureType);
|
|
view_type_items.push_back(EViewType::ColorPrint);
|
|
view_type_items.push_back(EViewType::Feedrate);
|
|
view_type_items.push_back(EViewType::Height);
|
|
view_type_items.push_back(EViewType::Width);
|
|
view_type_items.push_back(EViewType::VolumetricRate);
|
|
view_type_items.push_back(EViewType::LayerTime);
|
|
view_type_items.push_back(EViewType::FanSpeed);
|
|
view_type_items.push_back(EViewType::Temperature);
|
|
//if (mode == ConfigOptionMode::comDevelop) {
|
|
// view_type_items.push_back(EViewType::Tool);
|
|
//}
|
|
|
|
for (int i = 0; i < view_type_items.size(); i++) {
|
|
view_type_items_str.push_back(get_view_type_string(view_type_items[i]));
|
|
}
|
|
|
|
// BBS for first layer inspection
|
|
view_type_items.push_back(EViewType::FilamentId);
|
|
|
|
options_items.push_back(EMoveType::Travel);
|
|
options_items.push_back(EMoveType::Retract);
|
|
options_items.push_back(EMoveType::Unretract);
|
|
options_items.push_back(EMoveType::Wipe);
|
|
//if (mode == ConfigOptionMode::comDevelop) {
|
|
// options_items.push_back(EMoveType::Tool_change);
|
|
//}
|
|
//BBS: seam is not real move and extrusion, put at last line
|
|
options_items.push_back(EMoveType::Seam);
|
|
}
|
|
|
|
std::vector<int> GCodeViewer::get_plater_extruder()
|
|
{
|
|
return m_plater_extruder;
|
|
}
|
|
|
|
//BBS: always load shell at preview
|
|
void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, const BuildVolume& build_volume,
|
|
const std::vector<BoundingBoxf3>& exclude_bounding_box, bool initialized, ConfigOptionMode mode, bool only_gcode)
|
|
{
|
|
// avoid processing if called with the same gcode_result
|
|
if (m_last_result_id == gcode_result.id) {
|
|
//BBS: add logs
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": the same id %1%, return directly, result %2% ") % m_last_result_id % (&gcode_result);
|
|
return;
|
|
}
|
|
|
|
//BBS: add logs
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": gcode result %1%, new id %2%, gcode file %3% ") % (&gcode_result) % m_last_result_id % gcode_result.filename;
|
|
|
|
// release gpu memory, if used
|
|
reset();
|
|
|
|
//BBS: add mutex for protection of gcode result
|
|
gcode_result.lock();
|
|
//BBS: add safe check
|
|
if (gcode_result.moves.size() == 0) {
|
|
//result cleaned before slicing ,should return here
|
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": gcode result reset before, return directly!");
|
|
gcode_result.unlock();
|
|
return;
|
|
}
|
|
|
|
//BBS: move the id to the end of reset
|
|
m_last_result_id = gcode_result.id;
|
|
m_gcode_result = &gcode_result;
|
|
m_only_gcode_in_preview = only_gcode;
|
|
|
|
m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends);
|
|
|
|
//BBS: add only gcode mode
|
|
//if (wxGetApp().is_gcode_viewer())
|
|
if (m_only_gcode_in_preview)
|
|
m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z;
|
|
|
|
m_max_print_height = gcode_result.printable_height;
|
|
|
|
load_toolpaths(gcode_result, build_volume, exclude_bounding_box);
|
|
|
|
//BBS: add mutex for protection of gcode result
|
|
if (m_layers.empty()) {
|
|
gcode_result.unlock();
|
|
return;
|
|
}
|
|
|
|
m_settings_ids = gcode_result.settings_ids;
|
|
m_filament_diameters = gcode_result.filament_diameters;
|
|
m_filament_densities = gcode_result.filament_densities;
|
|
|
|
//BBS: always load shell at preview
|
|
/*if (wxGetApp().is_editor())
|
|
{
|
|
//load_shells(print, initialized);
|
|
}
|
|
else {*/
|
|
//BBS: add only gcode mode
|
|
if (m_only_gcode_in_preview) {
|
|
Pointfs printable_area;
|
|
//BBS: add bed exclude area
|
|
Pointfs bed_exclude_area = Pointfs();
|
|
std::string texture;
|
|
std::string model;
|
|
|
|
if (!gcode_result.printable_area.empty()) {
|
|
// bed shape detected in the gcode
|
|
printable_area = gcode_result.printable_area;
|
|
const auto bundle = wxGetApp().preset_bundle;
|
|
if (bundle != nullptr && !m_settings_ids.printer.empty()) {
|
|
const Preset* preset = bundle->printers.find_preset(m_settings_ids.printer);
|
|
if (preset != nullptr) {
|
|
model = PresetUtils::system_printer_bed_model(*preset);
|
|
texture = PresetUtils::system_printer_bed_texture(*preset);
|
|
}
|
|
}
|
|
|
|
//BBS: add bed exclude area
|
|
if (!gcode_result.bed_exclude_area.empty())
|
|
bed_exclude_area = gcode_result.bed_exclude_area;
|
|
|
|
wxGetApp().plater()->set_bed_shape(printable_area, bed_exclude_area, gcode_result.printable_height, texture, model, gcode_result.printable_area.empty());
|
|
}
|
|
/*else {
|
|
// adjust printbed size in dependence of toolpaths bbox
|
|
const double margin = 10.0;
|
|
const Vec2d min(m_paths_bounding_box.min.x() - margin, m_paths_bounding_box.min.y() - margin);
|
|
const Vec2d max(m_paths_bounding_box.max.x() + margin, m_paths_bounding_box.max.y() + margin);
|
|
|
|
const Vec2d size = max - min;
|
|
printable_area = {
|
|
{ min.x(), min.y() },
|
|
{ max.x(), min.y() },
|
|
{ max.x(), min.y() + 0.442265 * size.y()},
|
|
{ max.x() - 10.0, min.y() + 0.4711325 * size.y()},
|
|
{ max.x() + 10.0, min.y() + 0.5288675 * size.y()},
|
|
{ max.x(), min.y() + 0.557735 * size.y()},
|
|
{ max.x(), max.y() },
|
|
{ min.x() + 0.557735 * size.x(), max.y()},
|
|
{ min.x() + 0.5288675 * size.x(), max.y() - 10.0},
|
|
{ min.x() + 0.4711325 * size.x(), max.y() + 10.0},
|
|
{ min.x() + 0.442265 * size.x(), max.y()},
|
|
{ min.x(), max.y() } };
|
|
}*/
|
|
}
|
|
|
|
m_print_statistics = gcode_result.print_statistics;
|
|
|
|
if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) {
|
|
const float time = m_print_statistics.modes[static_cast<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;
|
|
|
|
bool only_gcode_3mf = false;
|
|
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
|
bool current_has_print_instances = current_plate->has_printable_instances();
|
|
if (current_plate->is_slice_result_valid() && wxGetApp().model().objects.empty() && !current_has_print_instances)
|
|
only_gcode_3mf = true;
|
|
m_layers_slider->set_menu_enable(!(only_gcode || only_gcode_3mf));
|
|
m_layers_slider->set_as_dirty();
|
|
m_moves_slider->set_as_dirty();
|
|
|
|
//BBS
|
|
m_conflict_result = gcode_result.conflict_result;
|
|
if (m_conflict_result) { m_conflict_result.value().layer = m_layers.get_l_at(m_conflict_result.value()._height); }
|
|
|
|
//BBS: add mutex for protection of gcode result
|
|
gcode_result.unlock();
|
|
//BBS: add logs
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished, m_buffers size %1%!")%m_buffers.size();
|
|
}
|
|
|
|
void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector<std::string>& str_tool_colors)
|
|
{
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
auto start_time = std::chrono::high_resolution_clock::now();
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
//BBS: add mutex for protection of gcode result
|
|
gcode_result.lock();
|
|
|
|
//BBS: add safe check
|
|
if (gcode_result.moves.size() == 0) {
|
|
//result cleaned before slicing ,should return here
|
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": gcode result reset before, return directly!");
|
|
gcode_result.unlock();
|
|
return;
|
|
}
|
|
|
|
//BBS: add mutex for protection of gcode result
|
|
if (m_moves_count == 0) {
|
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": gcode result m_moves_count is 0, return directly!");
|
|
gcode_result.unlock();
|
|
return;
|
|
}
|
|
|
|
wxBusyCursor busy;
|
|
|
|
if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) {
|
|
// update tool colors from config stored in the gcode
|
|
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;
|
|
}
|
|
|
|
for (int i = 0; i < m_tools.m_tool_colors.size(); i++) {
|
|
m_tools.m_tool_colors[i] = adjust_color_for_rendering(m_tools.m_tool_colors[i]);
|
|
}
|
|
// ensure there are enough colors defined
|
|
while (m_tools.m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) {
|
|
m_tools.m_tool_colors.push_back(decode_color("#FF8000"));
|
|
m_tools.m_tool_visibles.push_back(true);
|
|
}
|
|
|
|
// update ranges for coloring / legend
|
|
m_extrusions.reset_ranges();
|
|
for (size_t i = 0; i < m_moves_count; ++i) {
|
|
// skip first vertex
|
|
if (i == 0)
|
|
continue;
|
|
|
|
const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
|
|
|
|
switch (curr.type)
|
|
{
|
|
case EMoveType::Extrude:
|
|
{
|
|
m_extrusions.ranges.height.update_from(round_to_bin(curr.height));
|
|
m_extrusions.ranges.width.update_from(round_to_bin(curr.width));
|
|
m_extrusions.ranges.fan_speed.update_from(curr.fan_speed);
|
|
m_extrusions.ranges.temperature.update_from(curr.temperature);
|
|
if (curr.extrusion_role != erCustom || is_visible(erCustom))
|
|
m_extrusions.ranges.volumetric_rate.update_from(round_to_bin(curr.volumetric_rate()));
|
|
|
|
if (curr.layer_duration > 0.f) {
|
|
m_extrusions.ranges.layer_duration.update_from(curr.layer_duration);
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
case EMoveType::Travel:
|
|
{
|
|
if (m_buffers[buffer_id(curr.type)].visible)
|
|
m_extrusions.ranges.feedrate.update_from(curr.feedrate);
|
|
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
}
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
m_statistics.refresh_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
//BBS: add mutex for protection of gcode result
|
|
gcode_result.unlock();
|
|
|
|
// update buffers' render paths
|
|
refresh_render_paths();
|
|
log_memory_used("Refreshed G-code extrusion paths, ");
|
|
}
|
|
|
|
void GCodeViewer::refresh_render_paths()
|
|
{
|
|
refresh_render_paths(false, false);
|
|
}
|
|
|
|
void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config)
|
|
{
|
|
if (config != nullptr)
|
|
m_shells.volumes.update_colors_by_extruder(config,false);
|
|
}
|
|
|
|
//BBS: always load shell at preview
|
|
void GCodeViewer::reset_shell()
|
|
{
|
|
m_shells.volumes.clear();
|
|
m_shells.print_id = -1;
|
|
m_shell_bounding_box = BoundingBoxf3();
|
|
}
|
|
|
|
void GCodeViewer::reset()
|
|
{
|
|
//BBS: should also reset the result id
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": current result id %1% ")%m_last_result_id;
|
|
m_last_result_id = -1;
|
|
//BBS: add only gcode mode
|
|
m_only_gcode_in_preview = false;
|
|
|
|
m_moves_count = 0;
|
|
m_ssid_to_moveid_map.clear();
|
|
m_ssid_to_moveid_map.shrink_to_fit();
|
|
for (TBuffer& buffer : m_buffers) {
|
|
buffer.reset();
|
|
}
|
|
m_paths_bounding_box = BoundingBoxf3();
|
|
m_max_bounding_box = BoundingBoxf3();
|
|
m_max_print_height = 0.0f;
|
|
m_tools.m_tool_colors = std::vector<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();
|
|
|
|
m_legend_height = 0.0f;
|
|
if (m_roles.empty())
|
|
return;
|
|
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
render_toolpaths();
|
|
//render_shells();
|
|
render_legend(m_legend_height, canvas_width, canvas_height, right_margin);
|
|
|
|
if (m_user_mode != wxGetApp().get_mode()) {
|
|
update_by_mode(wxGetApp().get_mode());
|
|
m_user_mode = wxGetApp().get_mode();
|
|
}
|
|
|
|
//BBS fixed bottom_margin for space to render horiz slider
|
|
int bottom_margin = 64;
|
|
|
|
if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
|
|
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
|
|
m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
|
|
//BBS fixed buttom margin. m_moves_slider.pos_y
|
|
m_sequential_view.render(m_legend_height, canvas_width, canvas_height - bottom_margin * m_scale, right_margin * m_scale, m_view_type);
|
|
}
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
render_statistics();
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
//BBS render slider
|
|
render_slider(canvas_width, canvas_height);
|
|
}
|
|
|
|
#define ENABLE_CALIBRATION_THUMBNAIL_OUTPUT 0
|
|
#if ENABLE_CALIBRATION_THUMBNAIL_OUTPUT
|
|
static void debug_calibration_output_thumbnail(const ThumbnailData& thumbnail_data)
|
|
{
|
|
// debug export of generated image
|
|
wxImage image(thumbnail_data.width, thumbnail_data.height);
|
|
image.InitAlpha();
|
|
|
|
for (unsigned int r = 0; r < thumbnail_data.height; ++r)
|
|
{
|
|
unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width;
|
|
for (unsigned int c = 0; c < thumbnail_data.width; ++c)
|
|
{
|
|
unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c);
|
|
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
|
|
image.SetAlpha((int)c, (int)r, px[3]);
|
|
}
|
|
}
|
|
|
|
image.SaveFile("D:/calibrate.png", wxBITMAP_TYPE_PNG);
|
|
}
|
|
#endif
|
|
|
|
void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager)
|
|
{
|
|
int plate_idx = thumbnail_params.plate_id;
|
|
PartPlate* plate = partplate_list.get_plate(plate_idx);
|
|
BoundingBoxf3 plate_box = plate->get_bounding_box(false);
|
|
plate_box.min.z() = 0.0;
|
|
plate_box.max.z() = 0.0;
|
|
Vec3d center = plate_box.center();
|
|
|
|
#if 1
|
|
Camera camera;
|
|
camera.apply_viewport(0,0,thumbnail_data.width, thumbnail_data.height);
|
|
camera.set_scene_box(plate_box);
|
|
camera.set_type(Camera::EType::Ortho);
|
|
camera.set_target(center);
|
|
camera.select_view("top");
|
|
camera.apply_view_matrix();
|
|
camera.zoom_to_box(plate_box, 1.0f);
|
|
camera.apply_projection(plate_box);
|
|
|
|
auto render_as_triangles = [
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
this
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
](TBuffer &buffer, std::vector<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, 1;
|
|
// To be safe, we include both layers here although layer 1 seems enough
|
|
// layer 0: custom extrusions such as flow calibration etc.
|
|
// layer 1: the real first layer of object
|
|
std::array<unsigned int, 2> tmp_layers_z_range = m_layers_z_range;
|
|
m_layers_z_range = {0, 1};
|
|
// BBS exclude feature types
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags & ~(1 << erSkirt);
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags & ~(1 << erCustom);
|
|
// BBS include feature types
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erWipeTower);
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erPerimeter);
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erExternalPerimeter);
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erOverhangPerimeter);
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erSolidInfill);
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erTopSolidInfill);
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erInternalInfill);
|
|
m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erBottomSurface);
|
|
|
|
refresh_render_paths(false, false);
|
|
|
|
_render_calibration_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, partplate_list, opengl_manager);
|
|
|
|
// restore values and refresh render
|
|
// reset m_layers_z_range and view type
|
|
m_view_type_sel = last_view_type_sel;
|
|
set_view_type(last_view_type, false);
|
|
m_layers_z_range = tmp_layers_z_range;
|
|
m_extrusions.role_visibility_flags = last_role_visibility_flags;
|
|
refresh_render_paths(false, false);
|
|
}
|
|
|
|
bool GCodeViewer::can_export_toolpaths() const
|
|
{
|
|
return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle;
|
|
}
|
|
|
|
void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last)
|
|
{
|
|
auto is_visible = [this](unsigned int id) {
|
|
for (const TBuffer &buffer : m_buffers) {
|
|
if (buffer.visible) {
|
|
for (const Path &path : buffer.paths) {
|
|
if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const int first_diff = static_cast<int>(first) - static_cast<int>(m_sequential_view.last_current.first);
|
|
const int last_diff = static_cast<int>(last) - static_cast<int>(m_sequential_view.last_current.last);
|
|
|
|
unsigned int new_first = first;
|
|
unsigned int new_last = last;
|
|
|
|
if (m_sequential_view.skip_invisible_moves) {
|
|
while (!is_visible(new_first)) {
|
|
if (first_diff > 0)
|
|
++new_first;
|
|
else
|
|
--new_first;
|
|
}
|
|
|
|
while (!is_visible(new_last)) {
|
|
if (last_diff > 0)
|
|
++new_last;
|
|
else
|
|
--new_last;
|
|
}
|
|
}
|
|
|
|
m_sequential_view.current.first = new_first;
|
|
m_sequential_view.current.last = new_last;
|
|
m_sequential_view.last_current = m_sequential_view.current;
|
|
|
|
refresh_render_paths(true, true);
|
|
|
|
if (new_first != first || new_last != last) {
|
|
update_moves_slider();
|
|
}
|
|
}
|
|
|
|
void GCodeViewer::enable_moves_slider(bool enable) const
|
|
{
|
|
bool render_as_disabled = !enable;
|
|
if (m_moves_slider != nullptr && m_moves_slider->is_rendering_as_disabled() != render_as_disabled) {
|
|
m_moves_slider->set_render_as_disabled(render_as_disabled);
|
|
m_moves_slider->set_as_dirty();
|
|
}
|
|
}
|
|
|
|
void GCodeViewer::update_moves_slider(bool set_to_max)
|
|
{
|
|
const GCodeViewer::SequentialView &view = get_sequential_view();
|
|
// this should not be needed, but it is here to try to prevent rambling crashes on Mac Asan
|
|
if (view.endpoints.last < view.endpoints.first) return;
|
|
|
|
std::vector<double> values(view.endpoints.last - view.endpoints.first + 1);
|
|
std::vector<double> alternate_values(view.endpoints.last - view.endpoints.first + 1);
|
|
unsigned int count = 0;
|
|
for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i) {
|
|
values[count] = static_cast<double>(i + 1);
|
|
if (view.gcode_ids[i] > 0) alternate_values[count] = static_cast<double>(view.gcode_ids[i]);
|
|
++count;
|
|
}
|
|
|
|
m_moves_slider->SetSliderValues(values);
|
|
m_moves_slider->SetSliderAlternateValues(alternate_values);
|
|
m_moves_slider->SetMaxValue(view.endpoints.last - view.endpoints.first);
|
|
m_moves_slider->SetSelectionSpan(view.current.first - view.endpoints.first, view.current.last - view.endpoints.first);
|
|
if (set_to_max)
|
|
m_moves_slider->SetHigherValue(m_moves_slider->GetMaxValue());
|
|
}
|
|
|
|
void GCodeViewer::update_layers_slider_mode()
|
|
{
|
|
// true -> single-extruder printer profile OR
|
|
// multi-extruder printer profile , but whole model is printed by only one extruder
|
|
// false -> multi-extruder printer profile , and model is printed by several extruders
|
|
bool one_extruder_printed_model = true;
|
|
|
|
// extruder used for whole model for multi-extruder printer profile
|
|
int only_extruder = -1;
|
|
|
|
// BBS
|
|
if (wxGetApp().filaments_cnt() > 1) {
|
|
const ModelObjectPtrs &objects = wxGetApp().plater()->model().objects;
|
|
|
|
// check if whole model uses just only one extruder
|
|
if (!objects.empty()) {
|
|
const int extruder = objects[0]->config.has("extruder") ? objects[0]->config.option("extruder")->getInt() : 0;
|
|
|
|
auto is_one_extruder_printed_model = [objects, extruder]() {
|
|
for (ModelObject *object : objects) {
|
|
if (object->config.has("extruder") && object->config.option("extruder")->getInt() != extruder) return false;
|
|
|
|
for (ModelVolume *volume : object->volumes)
|
|
if ((volume->config.has("extruder") && volume->config.option("extruder")->getInt() != extruder) || !volume->mmu_segmentation_facets.empty()) return false;
|
|
|
|
for (const auto &range : object->layer_config_ranges)
|
|
if (range.second.has("extruder") && range.second.option("extruder")->getInt() != extruder) return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (is_one_extruder_printed_model())
|
|
only_extruder = extruder;
|
|
else
|
|
one_extruder_printed_model = false;
|
|
}
|
|
}
|
|
|
|
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
|
|
}
|
|
|
|
void GCodeViewer::update_marker_curr_move() {
|
|
if ((int)m_last_result_id != -1) {
|
|
auto it = std::find_if(m_gcode_result->moves.begin(), m_gcode_result->moves.end(), [this](auto move) {
|
|
if (m_sequential_view.current.last < m_sequential_view.gcode_ids.size() && m_sequential_view.current.last >= 0) {
|
|
return move.gcode_id == static_cast<uint64_t>(m_sequential_view.gcode_ids[m_sequential_view.current.last]);
|
|
}
|
|
return false;
|
|
});
|
|
if (it != m_gcode_result->moves.end())
|
|
m_sequential_view.marker.update_curr_move(*it);
|
|
}
|
|
}
|
|
|
|
bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const
|
|
{
|
|
size_t id = static_cast<size_t>(buffer_id(type));
|
|
return (id < m_buffers.size()) ? m_buffers[id].visible : false;
|
|
}
|
|
|
|
void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible)
|
|
{
|
|
size_t id = static_cast<size_t>(buffer_id(type));
|
|
if (id < m_buffers.size())
|
|
m_buffers[id].visible = visible;
|
|
}
|
|
|
|
unsigned int GCodeViewer::get_options_visibility_flags() const
|
|
{
|
|
auto set_flag = [](unsigned int flags, unsigned int flag, bool active) {
|
|
return active ? (flags | (1 << flag)) : flags;
|
|
};
|
|
|
|
unsigned int flags = 0;
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode));
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Shells), m_shells.visible);
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible());
|
|
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Legend), is_legend_enabled());
|
|
return flags;
|
|
}
|
|
|
|
void GCodeViewer::set_options_visibility_from_flags(unsigned int flags)
|
|
{
|
|
auto is_flag_set = [flags](unsigned int flag) {
|
|
return (flags & (1 << flag)) != 0;
|
|
};
|
|
|
|
set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Travel)));
|
|
set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Wipe)));
|
|
set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Retractions)));
|
|
set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Unretractions)));
|
|
set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Seams)));
|
|
set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolChanges)));
|
|
set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ColorChanges)));
|
|
set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast<unsigned int>(Preview::OptionType::PausePrints)));
|
|
set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast<unsigned int>(Preview::OptionType::CustomGCodes)));
|
|
m_shells.visible = is_flag_set(static_cast<unsigned int>(Preview::OptionType::Shells));
|
|
m_sequential_view.marker.set_visible(is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolMarker)));
|
|
enable_legend(is_flag_set(static_cast<unsigned int>(Preview::OptionType::Legend)));
|
|
}
|
|
|
|
void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range)
|
|
{
|
|
bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0];
|
|
bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1];
|
|
m_layers_z_range = layers_z_range;
|
|
refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last);
|
|
update_moves_slider(true);
|
|
}
|
|
|
|
void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
|
|
{
|
|
if (filename == nullptr)
|
|
return;
|
|
|
|
if (!has_data())
|
|
return;
|
|
|
|
wxBusyCursor busy;
|
|
|
|
// the data needed is contained into the Extrude TBuffer
|
|
const TBuffer& t_buffer = m_buffers[buffer_id(EMoveType::Extrude)];
|
|
if (!t_buffer.has_data())
|
|
return;
|
|
|
|
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle)
|
|
return;
|
|
|
|
// collect color information to generate materials
|
|
std::vector<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 ¤t = (i == loop_num? curr.position : curr.interpolation_points[i]);
|
|
const Vec3f dir = (current - previous).normalized();
|
|
Vec3f normal(dir.y(), -dir.x(), 0.0);
|
|
normal.normalize();
|
|
// add previous vertex
|
|
add_vertex(previous, normal);
|
|
// add current vertex
|
|
add_vertex(current, normal);
|
|
}
|
|
};
|
|
//BBS: modify a lot to support arc travel
|
|
auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
|
|
size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) {
|
|
|
|
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
|
|
buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1);
|
|
buffer.paths.back().sub_paths.front().first.position = prev.position;
|
|
}
|
|
|
|
Path& last_path = buffer.paths.back();
|
|
size_t loop_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() : 0;
|
|
for (size_t i = 0; i < loop_num + 1; i++) {
|
|
//BBS: add previous index
|
|
indices.push_back(static_cast<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> biased_seams_ids;
|
|
|
|
// toolpaths data -> extract vertices from result
|
|
for (size_t i = 0; i < m_moves_count; ++i) {
|
|
const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
|
|
if (curr.type == EMoveType::Seam) {
|
|
++seams_count;
|
|
biased_seams_ids.push_back(i - biased_seams_ids.size() - 1);
|
|
}
|
|
|
|
size_t move_id = i - seams_count;
|
|
|
|
// skip first vertex
|
|
if (i == 0)
|
|
continue;
|
|
|
|
const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1];
|
|
|
|
// update progress dialog
|
|
++progress_count;
|
|
if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
|
|
progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))),
|
|
_L("Generating geometry vertex data") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%");
|
|
progress_dialog->Fit();
|
|
progress_count = 0;
|
|
}
|
|
|
|
const unsigned char id = buffer_id(curr.type);
|
|
TBuffer& t_buffer = m_buffers[id];
|
|
MultiVertexBuffer& v_multibuffer = vertices[id];
|
|
InstanceBuffer& inst_buffer = instances[id];
|
|
InstanceIdBuffer& inst_id_buffer = instances_ids[id];
|
|
InstancesOffsets& inst_offsets = instances_offsets[id];
|
|
|
|
/*if (i%1000 == 1) {
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":i=%1%, buffer_id %2% render_type %3%, gcode_id %4%\n")
|
|
%i %(int)id %(int)t_buffer.render_primitive_type %curr.gcode_id;
|
|
}*/
|
|
|
|
// ensure there is at least one vertex buffer
|
|
if (v_multibuffer.empty())
|
|
v_multibuffer.push_back(VertexBuffer());
|
|
|
|
// if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer
|
|
// add another vertex buffer
|
|
// BBS: get the point number and then judge whether the remaining buffer is enough
|
|
size_t points_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() + 1 : 1;
|
|
size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : points_num * t_buffer.max_vertices_per_segment_size_bytes();
|
|
if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) {
|
|
v_multibuffer.push_back(VertexBuffer());
|
|
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
|
|
Path& last_path = t_buffer.paths.back();
|
|
if (prev.type == curr.type && last_path.matches(curr))
|
|
last_path.add_sub_path(prev, static_cast<unsigned int>(v_multibuffer.size()) - 1, 0, move_id - 1);
|
|
}
|
|
}
|
|
|
|
VertexBuffer& v_buffer = v_multibuffer.back();
|
|
|
|
switch (t_buffer.render_primitive_type)
|
|
{
|
|
case TBuffer::ERenderPrimitiveType::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 = [&biased_seams_ids](size_t id) {
|
|
size_t new_id = size_t(-1);
|
|
auto it = std::lower_bound(biased_seams_ids.begin(), biased_seams_ids.end(), id);
|
|
if (it == biased_seams_ids.end())
|
|
new_id = id + biased_seams_ids.size();
|
|
else {
|
|
if (it == biased_seams_ids.begin() && *it < id)
|
|
new_id = id;
|
|
else if (it != biased_seams_ids.begin())
|
|
new_id = id + std::distance(biased_seams_ids.begin(), it);
|
|
}
|
|
return (new_id == size_t(-1)) ? id : new_id;
|
|
};
|
|
//BBS: generate map from ssid to move id in advance to reduce computation
|
|
m_ssid_to_moveid_map.clear();
|
|
m_ssid_to_moveid_map.reserve( m_moves_count - biased_seams_ids.size());
|
|
for (size_t i = 0; i < m_moves_count - biased_seams_ids.size(); i++)
|
|
m_ssid_to_moveid_map.push_back(extract_move_id(i));
|
|
|
|
//BBS: smooth toolpaths corners for the given TBuffer using triangles
|
|
auto smooth_triangle_toolpaths_corners = [&gcode_result, this](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) {
|
|
auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) {
|
|
return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]);
|
|
};
|
|
auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) {
|
|
vertices[offset + 0] = position.x();
|
|
vertices[offset + 1] = position.y();
|
|
vertices[offset + 2] = position.z();
|
|
};
|
|
auto match_right_vertices_with_internal_point = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path,
|
|
size_t curr_s_id, bool is_internal_point, size_t interpolation_point_id, size_t vertex_size_floats, const Vec3f& displacement_vec) {
|
|
if (&prev_sub_path == &next_sub_path || is_internal_point) { // previous and next segment are both contained into to the same vertex buffer
|
|
VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id];
|
|
// offset into the vertex buffer of the next segment 1st vertex
|
|
size_t temp_offset = prev_sub_path.last.s_id - curr_s_id;
|
|
for (size_t i = prev_sub_path.last.s_id; i > curr_s_id; i--) {
|
|
size_t move_id = m_ssid_to_moveid_map[i];
|
|
temp_offset += (gcode_result.moves[move_id].is_arc_move() ? gcode_result.moves[move_id].interpolation_points.size() : 0);
|
|
}
|
|
if (is_internal_point) {
|
|
size_t move_id = m_ssid_to_moveid_map[curr_s_id];
|
|
temp_offset += (gcode_result.moves[move_id].interpolation_points.size() - interpolation_point_id);
|
|
}
|
|
const size_t next_1st_offset = temp_offset * 6 * vertex_size_floats;
|
|
// offset into the vertex buffer of the right vertex of the previous segment
|
|
const size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats;
|
|
// new position of the right vertices
|
|
const Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec;
|
|
// update previous segment
|
|
update_position_at(vbuffer, prev_right_offset, shared_vertex);
|
|
// offset into the vertex buffer of the right vertex of the next segment
|
|
const size_t next_right_offset = prev_sub_path.last.i_id - next_1st_offset;
|
|
// update next segment
|
|
update_position_at(vbuffer, next_right_offset, shared_vertex);
|
|
}
|
|
else { // previous and next segment are contained into different vertex buffers
|
|
VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id];
|
|
VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id];
|
|
// offset into the previous vertex buffer of the right vertex of the previous segment
|
|
const size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats;
|
|
// new position of the right vertices
|
|
const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec;
|
|
// update previous segment
|
|
update_position_at(prev_vbuffer, prev_right_offset, shared_vertex);
|
|
// offset into the next vertex buffer of the right vertex of the next segment
|
|
const size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats;
|
|
// update next segment
|
|
update_position_at(next_vbuffer, next_right_offset, shared_vertex);
|
|
}
|
|
};
|
|
//BBS: modify a lot of this function to support arc move
|
|
auto match_left_vertices_with_internal_point = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path,
|
|
size_t curr_s_id, bool is_internal_point, size_t interpolation_point_id, size_t vertex_size_floats, const Vec3f& displacement_vec) {
|
|
if (&prev_sub_path == &next_sub_path || is_internal_point) { // previous and next segment are both contained into to the same vertex buffer
|
|
VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id];
|
|
// offset into the vertex buffer of the next segment 1st vertex
|
|
size_t temp_offset = prev_sub_path.last.s_id - curr_s_id;
|
|
for (size_t i = prev_sub_path.last.s_id; i > curr_s_id; i--) {
|
|
size_t move_id = m_ssid_to_moveid_map[i];
|
|
temp_offset += (gcode_result.moves[move_id].is_arc_move() ? gcode_result.moves[move_id].interpolation_points.size() : 0);
|
|
}
|
|
if (is_internal_point) {
|
|
size_t move_id = m_ssid_to_moveid_map[curr_s_id];
|
|
temp_offset += (gcode_result.moves[move_id].interpolation_points.size() - interpolation_point_id);
|
|
}
|
|
const size_t next_1st_offset = temp_offset * 6 * vertex_size_floats;
|
|
// offset into the vertex buffer of the left vertex of the previous segment
|
|
const size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats;
|
|
// new position of the left vertices
|
|
const Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec;
|
|
// update previous segment
|
|
update_position_at(vbuffer, prev_left_offset, shared_vertex);
|
|
// offset into the vertex buffer of the left vertex of the next segment
|
|
const size_t next_left_offset = prev_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats;
|
|
// update next segment
|
|
update_position_at(vbuffer, next_left_offset, shared_vertex);
|
|
}
|
|
else { // previous and next segment are contained into different vertex buffers
|
|
VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id];
|
|
VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id];
|
|
// offset into the previous vertex buffer of the left vertex of the previous segment
|
|
const size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats;
|
|
// new position of the left vertices
|
|
const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec;
|
|
// update previous segment
|
|
update_position_at(prev_vbuffer, prev_left_offset, shared_vertex);
|
|
// offset into the next vertex buffer of the left vertex of the next segment
|
|
const size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats;
|
|
// update next segment
|
|
update_position_at(next_vbuffer, next_left_offset, shared_vertex);
|
|
}
|
|
};
|
|
|
|
size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats();
|
|
for (const Path& path : t_buffer.paths) {
|
|
//BBS: the two segments of the path sharing the current vertex may belong
|
|
//to two different vertex buffers
|
|
size_t prev_sub_path_id = 0;
|
|
size_t next_sub_path_id = 0;
|
|
const size_t path_vertices_count = path.vertices_count();
|
|
const float half_width = 0.5f * path.width;
|
|
// BBS: modify a lot to support arc move which has internal points
|
|
for (size_t j = 1; j < path_vertices_count; ++j) {
|
|
size_t curr_s_id = path.sub_paths.front().first.s_id + j;
|
|
size_t move_id = m_ssid_to_moveid_map[curr_s_id];
|
|
int interpolation_points_num = gcode_result.moves[move_id].is_arc_move_with_interpolation_points()?
|
|
gcode_result.moves[move_id].interpolation_points.size() : 0;
|
|
int loop_num = interpolation_points_num;
|
|
//BBS: select the subpaths which contains the previous/next segments
|
|
if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id))
|
|
++prev_sub_path_id;
|
|
if (j == path_vertices_count - 1) {
|
|
if (!gcode_result.moves[move_id].is_arc_move_with_interpolation_points())
|
|
break; // BBS: the last move has no internal point.
|
|
loop_num--; //BBS: don't need to handle the endpoint of the last arc move of path
|
|
next_sub_path_id = prev_sub_path_id;
|
|
} else {
|
|
if (!path.sub_paths[next_sub_path_id].contains(curr_s_id + 1))
|
|
++next_sub_path_id;
|
|
}
|
|
const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id];
|
|
const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id];
|
|
|
|
// BBS: smooth triangle toolpaths corners including arc move which has internal interpolation point
|
|
for (int k = 0; k <= loop_num; k++) {
|
|
const Vec3f& prev = k==0?
|
|
gcode_result.moves[move_id - 1].position :
|
|
gcode_result.moves[move_id].interpolation_points[k-1];
|
|
const Vec3f& curr = k==interpolation_points_num?
|
|
gcode_result.moves[move_id].position :
|
|
gcode_result.moves[move_id].interpolation_points[k];
|
|
const Vec3f& next = k < interpolation_points_num - 1?
|
|
gcode_result.moves[move_id].interpolation_points[k+1]:
|
|
(k == interpolation_points_num - 1? gcode_result.moves[move_id].position :
|
|
(gcode_result.moves[move_id + 1].is_arc_move_with_interpolation_points()?
|
|
gcode_result.moves[move_id + 1].interpolation_points[0] :
|
|
gcode_result.moves[move_id + 1].position));
|
|
|
|
const Vec3f prev_dir = (curr - prev).normalized();
|
|
const Vec3f prev_right = Vec3f(prev_dir.y(), -prev_dir.x(), 0.0f).normalized();
|
|
const Vec3f prev_up = prev_right.cross(prev_dir);
|
|
|
|
const Vec3f next_dir = (next - curr).normalized();
|
|
|
|
const bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f;
|
|
const float cos_dir = prev_dir.dot(next_dir);
|
|
// whether the angle between adjacent segments is greater than 45 degrees
|
|
const bool is_sharp = cos_dir < 0.7071068f;
|
|
|
|
float displacement = 0.0f;
|
|
if (cos_dir > -0.9998477f) {
|
|
// if the angle between adjacent segments is smaller than 179 degrees
|
|
Vec3f med_dir = (prev_dir + next_dir).normalized();
|
|
displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f)));
|
|
}
|
|
|
|
const float sq_prev_length = (curr - prev).squaredNorm();
|
|
const float sq_next_length = (next - curr).squaredNorm();
|
|
const float sq_displacement = sqr(displacement);
|
|
const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length&& sq_displacement < sq_next_length;
|
|
bool is_internal_point = interpolation_points_num > k;
|
|
|
|
if (can_displace) {
|
|
// displacement to apply to the vertices to match
|
|
Vec3f displacement_vec = displacement * prev_dir;
|
|
// matches inner corner vertices
|
|
if (is_right_turn)
|
|
match_right_vertices_with_internal_point(prev_sub_path, next_sub_path, curr_s_id, is_internal_point, k, vertex_size_floats, -displacement_vec);
|
|
else
|
|
match_left_vertices_with_internal_point(prev_sub_path, next_sub_path, curr_s_id, is_internal_point, k, vertex_size_floats, -displacement_vec);
|
|
|
|
if (!is_sharp) {
|
|
//BBS: matches outer corner vertices
|
|
if (is_right_turn)
|
|
match_left_vertices_with_internal_point(prev_sub_path, next_sub_path, curr_s_id, is_internal_point, k, vertex_size_floats, displacement_vec);
|
|
else
|
|
match_right_vertices_with_internal_point(prev_sub_path, next_sub_path, curr_s_id, is_internal_point, k, vertex_size_floats, displacement_vec);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
auto load_vertices_time = std::chrono::high_resolution_clock::now();
|
|
m_statistics.load_vertices = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
// smooth toolpaths corners for TBuffers using triangles
|
|
for (size_t i = 0; i < m_buffers.size(); ++i) {
|
|
const TBuffer& t_buffer = m_buffers[i];
|
|
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
|
|
smooth_triangle_toolpaths_corners(t_buffer, vertices[i]);
|
|
}
|
|
}
|
|
|
|
// dismiss, no more needed
|
|
std::vector<size_t>().swap(biased_seams_ids);
|
|
|
|
for (MultiVertexBuffer& v_multibuffer : vertices) {
|
|
for (VertexBuffer& v_buffer : v_multibuffer) {
|
|
v_buffer.shrink_to_fit();
|
|
}
|
|
}
|
|
|
|
// move the wipe toolpaths half height up to render them on proper position
|
|
MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)];
|
|
for (VertexBuffer& v_buffer : wipe_vertices) {
|
|
for (size_t i = 2; i < v_buffer.size(); i += 3) {
|
|
v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height;
|
|
}
|
|
}
|
|
|
|
// send vertices data to gpu, where needed
|
|
for (size_t i = 0; i < m_buffers.size(); ++i) {
|
|
TBuffer& t_buffer = m_buffers[i];
|
|
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) {
|
|
const InstanceBuffer& inst_buffer = instances[i];
|
|
if (!inst_buffer.empty()) {
|
|
t_buffer.model.instances.buffer = inst_buffer;
|
|
t_buffer.model.instances.s_ids = instances_ids[i];
|
|
t_buffer.model.instances.offsets = instances_offsets[i];
|
|
}
|
|
}
|
|
else {
|
|
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
|
|
const InstanceBuffer& inst_buffer = instances[i];
|
|
if (!inst_buffer.empty()) {
|
|
t_buffer.model.instances.buffer = inst_buffer;
|
|
t_buffer.model.instances.s_ids = instances_ids[i];
|
|
t_buffer.model.instances.offsets = instances_offsets[i];
|
|
}
|
|
}
|
|
const MultiVertexBuffer& v_multibuffer = vertices[i];
|
|
for (const VertexBuffer& v_buffer : v_multibuffer) {
|
|
const size_t size_elements = v_buffer.size();
|
|
const size_t size_bytes = size_elements * sizeof(float);
|
|
const size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats();
|
|
t_buffer.vertices.count += vertices_count;
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
m_statistics.total_vertices_gpu_size += static_cast<int64_t>(size_bytes);
|
|
m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast<int64_t>(size_bytes));
|
|
++m_statistics.vbuffers_count;
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
GLuint id = 0;
|
|
glsafe(::glGenBuffers(1, &id));
|
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id));
|
|
glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW));
|
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
|
|
|
t_buffer.vertices.vbos.push_back(static_cast<unsigned int>(id));
|
|
t_buffer.vertices.sizes.push_back(size_bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
auto smooth_vertices_time = std::chrono::high_resolution_clock::now();
|
|
m_statistics.smooth_vertices = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - load_vertices_time).count();
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices);
|
|
|
|
// dismiss vertices data, no more needed
|
|
std::vector<MultiVertexBuffer>().swap(vertices);
|
|
std::vector<InstanceBuffer>().swap(instances);
|
|
std::vector<InstanceIdBuffer>().swap(instances_ids);
|
|
|
|
// toolpaths data -> extract indices from result
|
|
// paths may have been filled while extracting vertices,
|
|
// so reset them, they will be filled again while extracting indices
|
|
for (TBuffer& buffer : m_buffers) {
|
|
buffer.paths.clear();
|
|
}
|
|
|
|
// variable used to keep track of the current vertex buffers index and size
|
|
using CurrVertexBuffer = std::pair<unsigned int, size_t>;
|
|
std::vector<CurrVertexBuffer> curr_vertex_buffers(m_buffers.size(), { 0, 0 });
|
|
|
|
// variable used to keep track of the vertex buffers ids
|
|
using VboIndexList = std::vector<unsigned int>;
|
|
std::vector<VboIndexList> vbo_indices(m_buffers.size());
|
|
|
|
seams_count = 0;
|
|
|
|
for (size_t i = 0; i < m_moves_count; ++i) {
|
|
const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
|
|
if (curr.type == EMoveType::Seam)
|
|
++seams_count;
|
|
|
|
size_t move_id = i - seams_count;
|
|
|
|
// skip first vertex
|
|
if (i == 0)
|
|
continue;
|
|
|
|
const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1];
|
|
const GCodeProcessorResult::MoveVertex* next = nullptr;
|
|
if (i < m_moves_count - 1)
|
|
next = &gcode_result.moves[i + 1];
|
|
|
|
++progress_count;
|
|
if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
|
|
progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))),
|
|
_L("Generating geometry index data") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%");
|
|
progress_dialog->Fit();
|
|
progress_count = 0;
|
|
}
|
|
|
|
const unsigned char id = buffer_id(curr.type);
|
|
TBuffer& t_buffer = m_buffers[id];
|
|
MultiIndexBuffer& i_multibuffer = indices[id];
|
|
CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id];
|
|
VboIndexList& vbo_index_list = vbo_indices[id];
|
|
|
|
// ensure there is at least one index buffer
|
|
if (i_multibuffer.empty()) {
|
|
i_multibuffer.push_back(IndexBuffer());
|
|
if (!t_buffer.vertices.vbos.empty())
|
|
vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
|
|
}
|
|
|
|
// if adding the indices for the current segment exceeds the threshold size of the current index buffer
|
|
// create another index buffer
|
|
// BBS: get the point number and then judge whether the remaining buffer is enough
|
|
size_t points_num = curr.is_arc_move_with_interpolation_points() ? curr.interpolation_points.size() + 1 : 1;
|
|
size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : points_num * t_buffer.max_indices_per_segment_size_bytes();
|
|
if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) {
|
|
i_multibuffer.push_back(IndexBuffer());
|
|
vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
|
|
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::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();
|
|
|
|
std::vector<int> plater_extruder;
|
|
for (auto mid : m_extruder_ids){
|
|
int eid = mid;
|
|
plater_extruder.push_back(++eid);
|
|
}
|
|
m_plater_extruder = plater_extruder;
|
|
|
|
// replace layers for spiral vase mode
|
|
if (!gcode_result.spiral_vase_layers.empty()) {
|
|
m_layers.reset();
|
|
for (const auto& layer : gcode_result.spiral_vase_layers) {
|
|
m_layers.append(layer.first, { layer.second.first, layer.second.second });
|
|
}
|
|
}
|
|
|
|
// set layers z range
|
|
if (!m_layers.empty())
|
|
m_layers_z_range = { 0, static_cast<unsigned int>(m_layers.size() - 1) };
|
|
|
|
// change color of paths whose layer contains option points
|
|
if (!options_zs.empty()) {
|
|
TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)];
|
|
for (Path& path : extrude_buffer.paths) {
|
|
const float z = path.sub_paths.front().first.position.z();
|
|
if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end())
|
|
path.cp_color_id = 255 - path.cp_color_id;
|
|
}
|
|
}
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
m_statistics.load_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
if (progress_dialog != nullptr)
|
|
progress_dialog->Destroy();
|
|
}
|
|
|
|
//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<unsigned int> print_extruders = print.extruders(true);
|
|
int extruders_count = print_extruders.size();
|
|
|
|
const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2);
|
|
const PrintConfig& config = print.config();
|
|
if (config.enable_prime_tower &&
|
|
(print.enable_timelapse_print() || (extruders_count > 1 && (config.print_sequence == PrintSequence::ByLayer)))) {
|
|
const float depth = print.wipe_tower_data(extruders_count).depth;
|
|
const float brim_width = print.wipe_tower_data(extruders_count).brim_width;
|
|
|
|
int plate_idx = print.get_plate_index();
|
|
Vec3d plate_origin = print.get_plate_origin();
|
|
double wipe_tower_x = config.wipe_tower_x.get_at(plate_idx) + plate_origin(0);
|
|
double wipe_tower_y = config.wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
|
m_shells.volumes.load_wipe_tower_preview(1000, wipe_tower_x, wipe_tower_y, config.prime_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
|
|
!print.is_step_done(psWipeTower), brim_width, initialized);
|
|
}
|
|
}
|
|
|
|
// remove modifiers
|
|
while (true) {
|
|
GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; });
|
|
if (it != m_shells.volumes.volumes.end()) {
|
|
m_shells.volumes.release_volume(*it);
|
|
delete (*it);
|
|
m_shells.volumes.volumes.erase(it);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
for (GLVolume* volume : m_shells.volumes.volumes) {
|
|
volume->zoom_to_volumes = false;
|
|
volume->color[3] = 0.5f;
|
|
volume->force_native_color = true;
|
|
volume->set_render_color();
|
|
//BBS: add shell bounding box logic
|
|
m_shell_bounding_box.merge(volume->transformed_bounding_box());
|
|
}
|
|
|
|
//BBS: always load shell when preview
|
|
m_shells.print_id = print.id().id;
|
|
m_shells.print_modify_count = print.get_modified_count();
|
|
m_shells.previewing = true;
|
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": shell loaded, id change to %1%, modify_count %2%, object count %3%, glvolume count %4%")
|
|
% m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size();
|
|
}
|
|
|
|
void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const
|
|
{
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
auto start_time = std::chrono::high_resolution_clock::now();
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": enter, m_buffers size %1%!")%m_buffers.size();
|
|
auto extrusion_color = [this](const Path& path) {
|
|
Color color;
|
|
switch (m_view_type)
|
|
{
|
|
case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast<unsigned int>(path.role)]; break; }
|
|
case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; }
|
|
case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; }
|
|
case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; }
|
|
case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; }
|
|
case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; }
|
|
case EViewType::LayerTime: { color = m_extrusions.ranges.layer_duration.get_color_at(path.layer_time, Extrusions::Range::EType::Logarithmic); break; }
|
|
case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; }
|
|
case EViewType::Tool: { color = m_tools.m_tool_colors[path.extruder_id]; break; }
|
|
case EViewType::ColorPrint: {
|
|
if (path.cp_color_id >= static_cast<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];
|
|
color = adjust_color_for_rendering(color);
|
|
}
|
|
break;
|
|
}
|
|
case EViewType::FilamentId: {
|
|
float id = float(path.extruder_id)/256;
|
|
float role = float(path.role) / 256;
|
|
color = {id, role, id, 1.0f};
|
|
break;
|
|
}
|
|
default: { color = { 1.0f, 1.0f, 1.0f, 1.0f }; break; }
|
|
}
|
|
|
|
return color;
|
|
};
|
|
|
|
auto travel_color = [](const Path& path) {
|
|
return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ :
|
|
((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ :
|
|
Travel_Colors[0] /* Move */);
|
|
};
|
|
|
|
auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) {
|
|
auto in_layers_range = [this, min_id, max_id](size_t id) {
|
|
return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last;
|
|
};
|
|
|
|
return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id);
|
|
};
|
|
|
|
//BBS
|
|
auto is_extruder_in_layer_range = [this](const Path& path, size_t extruder_id) {
|
|
return path.extruder_id == extruder_id;
|
|
};
|
|
|
|
|
|
auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) {
|
|
const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)];
|
|
if (path_id >= buffer.paths.size())
|
|
return false;
|
|
|
|
Path path = buffer.paths[path_id];
|
|
size_t first = path_id;
|
|
size_t last = path_id;
|
|
|
|
// check adjacent paths
|
|
while (first > 0 && path.sub_paths.front().first.position.isApprox(buffer.paths[first - 1].sub_paths.back().last.position)) {
|
|
--first;
|
|
path.sub_paths.front().first = buffer.paths[first].sub_paths.front().first;
|
|
}
|
|
while (last < buffer.paths.size() - 1 && path.sub_paths.back().last.position.isApprox(buffer.paths[last + 1].sub_paths.front().first.position)) {
|
|
++last;
|
|
path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last;
|
|
}
|
|
|
|
const size_t min_s_id = m_layers.get_endpoints_at(min_id).first;
|
|
const size_t max_s_id = m_layers.get_endpoints_at(max_id).last;
|
|
|
|
return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) ||
|
|
(min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id);
|
|
};
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
Statistics* statistics = const_cast<Statistics*>(&m_statistics);
|
|
statistics->render_paths_size = 0;
|
|
statistics->models_instances_size = 0;
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
const bool top_layer_only = true;
|
|
|
|
//BBS
|
|
SequentialView::Endpoints global_endpoints = { m_sequential_view.gcode_ids.size() , 0 };
|
|
SequentialView::Endpoints top_layer_endpoints = global_endpoints;
|
|
SequentialView* sequential_view = const_cast<SequentialView*>(&m_sequential_view);
|
|
if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0;
|
|
//BBS
|
|
if (!keep_sequential_current_last) sequential_view->current.last = m_sequential_view.gcode_ids.size();
|
|
|
|
// first pass: collect visible paths and update sequential view data
|
|
std::vector<std::tuple<unsigned char, unsigned int, unsigned int, unsigned int>> paths;
|
|
|
|
for (size_t b = 0; b < m_buffers.size(); ++b) {
|
|
TBuffer& buffer = const_cast<TBuffer&>(m_buffers[b]);
|
|
// reset render paths
|
|
buffer.render_paths.clear();
|
|
|
|
if (!buffer.visible)
|
|
continue;
|
|
|
|
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel ||
|
|
buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
|
|
for (size_t id : buffer.model.instances.s_ids) {
|
|
if (id < m_layers.get_endpoints_at(m_layers_z_range[0]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id)
|
|
continue;
|
|
|
|
global_endpoints.first = std::min(global_endpoints.first, id);
|
|
global_endpoints.last = std::max(global_endpoints.last, id);
|
|
|
|
if (top_layer_only) {
|
|
if (id < m_layers.get_endpoints_at(m_layers_z_range[1]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id)
|
|
continue;
|
|
|
|
top_layer_endpoints.first = std::min(top_layer_endpoints.first, id);
|
|
top_layer_endpoints.last = std::max(top_layer_endpoints.last, id);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (size_t i = 0; i < buffer.paths.size(); ++i) {
|
|
const Path& path = buffer.paths[i];
|
|
if (path.type == EMoveType::Travel) {
|
|
if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1]))
|
|
continue;
|
|
}
|
|
else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1]))
|
|
continue;
|
|
|
|
if (path.type == EMoveType::Extrude && !is_visible(path))
|
|
continue;
|
|
|
|
if (m_view_type == EViewType::ColorPrint && !m_tools.m_tool_visibles[path.extruder_id])
|
|
continue;
|
|
|
|
// store valid path
|
|
for (size_t j = 0; j < path.sub_paths.size(); ++j) {
|
|
paths.push_back({ static_cast<unsigned char>(b), path.sub_paths[j].first.b_id, static_cast<unsigned int>(i), static_cast<unsigned int>(j) });
|
|
}
|
|
|
|
global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id);
|
|
global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id);
|
|
|
|
if (top_layer_only) {
|
|
if (path.type == EMoveType::Travel) {
|
|
if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) {
|
|
top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id);
|
|
top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id);
|
|
}
|
|
}
|
|
else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) {
|
|
top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id);
|
|
top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update current sequential position
|
|
sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first;
|
|
if (global_endpoints.last == 0) {
|
|
sequential_view->current.last = global_endpoints.last;
|
|
} else {
|
|
sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last;
|
|
}
|
|
|
|
// get the world position from the vertex buffer
|
|
bool found = false;
|
|
for (const TBuffer& buffer : m_buffers) {
|
|
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel ||
|
|
buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
|
|
for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) {
|
|
if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) {
|
|
size_t offset = i * buffer.model.instances.instance_size_floats();
|
|
sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0];
|
|
sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1];
|
|
sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2];
|
|
sequential_view->current_offset = buffer.model.instances.offsets[i];
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// searches the path containing the current position
|
|
for (const Path& path : buffer.paths) {
|
|
if (path.contains(m_sequential_view.current.last)) {
|
|
const int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last);
|
|
if (sub_path_id != -1) {
|
|
const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id];
|
|
unsigned int offset = static_cast<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>::reverse_iterator it_path, std::vector<RenderPath>::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) {
|
|
glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE));
|
|
glsafe(::glEnable(GL_POINT_SPRITE));
|
|
|
|
for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
|
|
const RenderPath& path = *it;
|
|
// Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415.
|
|
assert(! path.sizes.empty());
|
|
assert(! path.offsets.empty());
|
|
glsafe(::glUniform4fv(uniform_color, 1, static_cast<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>::reverse_iterator it_path, std::vector<RenderPath>::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) {
|
|
for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
|
|
const RenderPath& path = *it;
|
|
// Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415.
|
|
assert(! path.sizes.empty());
|
|
assert(! path.offsets.empty());
|
|
glsafe(::glUniform4fv(uniform_color, 1, static_cast<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>::reverse_iterator it_path, std::vector<RenderPath>::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) {
|
|
for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
|
|
const RenderPath& path = *it;
|
|
// Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415.
|
|
assert(! path.sizes.empty());
|
|
assert(! path.offsets.empty());
|
|
glsafe(::glUniform4fv(uniform_color, 1, static_cast<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.rbegin();
|
|
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":buffer indices size %1%, render_path size %2% ")%buffer.indices.size() %buffer.render_paths.size();
|
|
unsigned int indices_count = static_cast<unsigned int>(buffer.indices.size());
|
|
for (unsigned int index = 0; index < indices_count; ++index) {
|
|
unsigned int ibuffer_id = indices_count - index - 1;
|
|
const IBuffer& i_buffer = buffer.indices[ibuffer_id];
|
|
// Skip all paths with ibuffer_id < ibuffer_id.
|
|
for (; it_path != buffer.render_paths.rend() && it_path->ibuffer_id > ibuffer_id; ++ it_path) ;
|
|
if (it_path == buffer.render_paths.rend() || it_path->ibuffer_id < ibuffer_id)
|
|
// Not found. This shall not happen.
|
|
continue;
|
|
|
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
|
|
glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
|
|
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
|
bool has_normals = buffer.vertices.normal_size_floats() > 0;
|
|
if (has_normals) {
|
|
glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
|
|
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
|
}
|
|
|
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
|
|
|
|
// Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors.
|
|
switch (buffer.render_primitive_type)
|
|
{
|
|
case TBuffer::ERenderPrimitiveType::Point: {
|
|
render_as_points(it_path, buffer.render_paths.rend(), *shader, uniform_color);
|
|
break;
|
|
}
|
|
case TBuffer::ERenderPrimitiveType::Line: {
|
|
glsafe(::glLineWidth(static_cast<GLfloat>(line_width(zoom))));
|
|
render_as_lines(it_path, buffer.render_paths.rend(), *shader, uniform_color);
|
|
break;
|
|
}
|
|
case TBuffer::ERenderPrimitiveType::Triangle: {
|
|
render_as_triangles(it_path, buffer.render_paths.rend(), *shader, uniform_color);
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
|
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
|
|
|
if (has_normals)
|
|
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
|
|
|
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
|
}
|
|
}
|
|
|
|
shader->stop_using();
|
|
}
|
|
}
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
auto render_sequential_range_cap = [this]
|
|
#else
|
|
auto render_sequential_range_cap = []
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
(const SequentialRangeCap& cap) {
|
|
GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str());
|
|
if (shader != nullptr) {
|
|
shader->start_using();
|
|
|
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo));
|
|
glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_bytes()));
|
|
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
|
bool has_normals = cap.buffer->vertices.normal_size_floats() > 0;
|
|
if (has_normals) {
|
|
glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_bytes()));
|
|
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
|
}
|
|
|
|
shader->set_uniform("uniform_color", cap.color);
|
|
|
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo));
|
|
glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr));
|
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
++m_statistics.gl_triangles_calls_count;
|
|
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
|
|
|
if (has_normals)
|
|
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
|
|
|
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
|
|
|
shader->stop_using();
|
|
}
|
|
};
|
|
|
|
for (unsigned int i = 0; i < 2; ++i) {
|
|
if (m_sequential_range_caps[i].is_renderable())
|
|
render_sequential_range_cap(m_sequential_range_caps[i]);
|
|
}
|
|
}
|
|
|
|
void GCodeViewer::render_shells()
|
|
{
|
|
//BBS: add shell previewing logic
|
|
if ((!m_shells.previewing && !m_shells.visible) || m_shells.volumes.empty())
|
|
//if (!m_shells.visible || m_shells.volumes.empty())
|
|
return;
|
|
|
|
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
// when the background processing is enabled, it may happen that the shells data have been loaded
|
|
// before opengl has been initialized for the preview canvas.
|
|
// when this happens, the volumes' data have not been sent to gpu yet.
|
|
for (GLVolume* v : m_shells.volumes.volumes) {
|
|
if (!v->indexed_vertex_array->has_VBOs())
|
|
v->finalize_geometry(true);
|
|
}
|
|
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
// glsafe(::glDepthMask(GL_FALSE));
|
|
|
|
shader->start_using();
|
|
//BBS: reopen cul faces
|
|
m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
|
|
shader->stop_using();
|
|
|
|
// glsafe(::glDepthMask(GL_TRUE));
|
|
}
|
|
|
|
//BBS
|
|
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const {
|
|
if (!show)
|
|
return;
|
|
for (auto gcode_result : gcode_result_list) {
|
|
if (gcode_result->moves.size() == 0)
|
|
return;
|
|
}
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0, 10.0 * m_scale));
|
|
ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f, 1.0f, 1.0f, 0.6f));
|
|
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(340.f * m_scale * imgui.scaled(1.0f / 15.0f), 0));
|
|
|
|
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), 0, ImVec2(0.5f, 0.5f));
|
|
ImGui::Begin(_L("Statistics of All Plates").c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
|
|
std::vector<float> filament_diameters = gcode_result_list.front()->filament_diameters;
|
|
std::vector<float> filament_densities = gcode_result_list.front()->filament_densities;
|
|
std::vector<Color> filament_colors = decode_colors(wxGetApp().plater()->get_extruder_colors_from_plater_config(gcode_result_list.back()));
|
|
|
|
for (int i = 0; i < filament_colors.size(); i++) {
|
|
filament_colors[i] = adjust_color_for_rendering(filament_colors[i]);
|
|
}
|
|
|
|
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
|
float window_padding = 4.0f * m_scale;
|
|
const float icon_size = ImGui::GetTextLineHeight() * 0.7;
|
|
std::map<std::string, float> offsets;
|
|
std::map<int, double> model_volume_of_extruders_all_plates; // map<extruder_idx, volume>
|
|
std::map<int, double> flushed_volume_of_extruders_all_plates; // map<extruder_idx, flushed volume>
|
|
std::map<int, double> wipe_tower_volume_of_extruders_all_plates; // map<extruder_idx, flushed volume>
|
|
std::map<int, double> support_volume_of_extruders_all_plates; // map<extruder_idx, flushed volume>
|
|
std::map<int, double> plate_time; // map<plate_idx, time>
|
|
std::vector<double> model_used_filaments_m_all_plates;
|
|
std::vector<double> model_used_filaments_g_all_plates;
|
|
std::vector<double> flushed_filaments_m_all_plates;
|
|
std::vector<double> flushed_filaments_g_all_plates;
|
|
std::vector<double> wipe_tower_used_filaments_m_all_plates;
|
|
std::vector<double> wipe_tower_used_filaments_g_all_plates;
|
|
std::vector<double> support_used_filaments_m_all_plates;
|
|
std::vector<double> support_used_filaments_g_all_plates;
|
|
float total_time_all_plates = 0.0f;
|
|
float total_cost_all_plates = 0.0f;
|
|
double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1.0;
|
|
struct ColumnData {
|
|
enum {
|
|
Model = 1,
|
|
Flushed = 2,
|
|
WipeTower = 4,
|
|
Support = 1 << 3,
|
|
};
|
|
};
|
|
int displayed_columns = 0;
|
|
|
|
auto max_width = [](const std::vector<std::string>& items, const std::string& title, float extra_size = 0.0f) {
|
|
float ret = ImGui::CalcTextSize(title.c_str()).x;
|
|
for (const std::string& item : items) {
|
|
ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x);
|
|
}
|
|
return ret;
|
|
};
|
|
auto calculate_offsets = [max_width, window_padding](const std::vector<std::pair<std::string, std::vector<::string>>>& title_columns, float extra_size = 0.0f) {
|
|
const ImGuiStyle& style = ImGui::GetStyle();
|
|
std::vector<float> offsets;
|
|
offsets.push_back(max_width(title_columns[0].second, title_columns[0].first, extra_size) + 3.0f * style.ItemSpacing.x + style.WindowPadding.x);
|
|
for (size_t i = 1; i < title_columns.size() - 1; i++)
|
|
offsets.push_back(offsets.back() + max_width(title_columns[i].second, title_columns[i].first) + style.ItemSpacing.x);
|
|
if (title_columns.back().first == _u8L("Display"))
|
|
offsets.back() = ImGui::GetWindowWidth() - ImGui::CalcTextSize(_u8L("Display").c_str()).x - ImGui::GetFrameHeight() / 2 - 2 * window_padding;
|
|
|
|
float average_col_width = ImGui::GetWindowWidth() / static_cast<float>(title_columns.size());
|
|
std::vector<float> ret;
|
|
ret.push_back(0);
|
|
for (size_t i = 1; i < title_columns.size(); i++) {
|
|
ret.push_back(std::max(offsets[i - 1], i * average_col_width));
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
auto append_item = [icon_size, &imgui, imperial_units, &window_padding, &draw_list, this](bool draw_icon, const Color& color, const std::vector<std::pair<std::string, float>>& columns_offsets)
|
|
{
|
|
// render icon
|
|
ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y);
|
|
|
|
if (draw_icon)
|
|
draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale },
|
|
ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }));
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0 * m_scale, 6.0 * m_scale));
|
|
|
|
// render selectable
|
|
ImGui::Dummy({ 0.0, 0.0 });
|
|
ImGui::SameLine();
|
|
|
|
// render column item
|
|
{
|
|
float dummy_size = draw_icon ? ImGui::GetStyle().ItemSpacing.x + icon_size : window_padding * 3;
|
|
ImGui::SameLine(dummy_size);
|
|
imgui.text(columns_offsets[0].first);
|
|
|
|
for (auto i = 1; i < columns_offsets.size(); i++) {
|
|
ImGui::SameLine(columns_offsets[i].second);
|
|
imgui.text(columns_offsets[i].first);
|
|
}
|
|
}
|
|
|
|
ImGui::PopStyleVar(1);
|
|
};
|
|
auto append_headers = [&imgui](const std::vector<std::pair<std::string, float>>& title_offsets) {
|
|
for (size_t i = 0; i < title_offsets.size(); i++) {
|
|
ImGui::SameLine(title_offsets[i].second);
|
|
imgui.bold_text(title_offsets[i].first);
|
|
}
|
|
ImGui::Separator();
|
|
};
|
|
auto get_used_filament_from_volume = [this, imperial_units, &filament_diameters, &filament_densities](double volume, int extruder_id) {
|
|
double koef = imperial_units ? 1.0 / GizmoObjectManipulation::in_to_mm : 0.001;
|
|
std::pair<double, double> ret = { koef * volume / (PI * sqr(0.5 * filament_diameters[extruder_id])),
|
|
volume * filament_densities[extruder_id] * 0.001 };
|
|
return ret;
|
|
};
|
|
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
// title and item data
|
|
{
|
|
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
|
|
for (auto plate : plate_list.get_nonempty_plate_list())
|
|
{
|
|
auto plate_print_statistics = plate->get_slice_result()->print_statistics;
|
|
auto plate_extruders = plate->get_extruders(true);
|
|
for (size_t extruder_id : plate_extruders) {
|
|
extruder_id -= 1;
|
|
if (plate_print_statistics.model_volumes_per_extruder.find(extruder_id) == plate_print_statistics.model_volumes_per_extruder.end())
|
|
model_volume_of_extruders_all_plates[extruder_id] += 0;
|
|
else {
|
|
double model_volume = plate_print_statistics.model_volumes_per_extruder.at(extruder_id);
|
|
model_volume_of_extruders_all_plates[extruder_id] += model_volume;
|
|
}
|
|
if (plate_print_statistics.flush_per_filament.find(extruder_id) == plate_print_statistics.flush_per_filament.end())
|
|
flushed_volume_of_extruders_all_plates[extruder_id] += 0;
|
|
else {
|
|
double flushed_volume = plate_print_statistics.flush_per_filament.at(extruder_id);
|
|
flushed_volume_of_extruders_all_plates[extruder_id] += flushed_volume;
|
|
}
|
|
if (plate_print_statistics.wipe_tower_volumes_per_extruder.find(extruder_id) == plate_print_statistics.wipe_tower_volumes_per_extruder.end())
|
|
wipe_tower_volume_of_extruders_all_plates[extruder_id] += 0;
|
|
else {
|
|
double wipe_tower_volume = plate_print_statistics.wipe_tower_volumes_per_extruder.at(extruder_id);
|
|
wipe_tower_volume_of_extruders_all_plates[extruder_id] += wipe_tower_volume;
|
|
}
|
|
if (plate_print_statistics.support_volumes_per_extruder.find(extruder_id) == plate_print_statistics.support_volumes_per_extruder.end())
|
|
support_volume_of_extruders_all_plates[extruder_id] += 0;
|
|
else {
|
|
double support_volume = plate_print_statistics.support_volumes_per_extruder.at(extruder_id);
|
|
support_volume_of_extruders_all_plates[extruder_id] += support_volume;
|
|
}
|
|
}
|
|
const PrintEstimatedStatistics::Mode& plate_time_mode = plate_print_statistics.modes[static_cast<size_t>(m_time_estimate_mode)];
|
|
plate_time.insert_or_assign(plate->get_index(), plate_time_mode.time);
|
|
total_time_all_plates += plate_time_mode.time;
|
|
|
|
Print* print;
|
|
plate->get_print((PrintBase**)&print, nullptr, nullptr);
|
|
total_cost_all_plates += print->print_statistics().total_cost;
|
|
}
|
|
|
|
for (auto it = model_volume_of_extruders_all_plates.begin(); it != model_volume_of_extruders_all_plates.end(); it++) {
|
|
auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(it->second, it->first);
|
|
if (model_used_filament_m != 0.0 || model_used_filament_g != 0.0)
|
|
displayed_columns |= ColumnData::Model;
|
|
model_used_filaments_m_all_plates.push_back(model_used_filament_m);
|
|
model_used_filaments_g_all_plates.push_back(model_used_filament_g);
|
|
}
|
|
for (auto it = flushed_volume_of_extruders_all_plates.begin(); it != flushed_volume_of_extruders_all_plates.end(); it++) {
|
|
auto [flushed_filament_m, flushed_filament_g] = get_used_filament_from_volume(it->second, it->first);
|
|
if (flushed_filament_m != 0.0 || flushed_filament_g != 0.0)
|
|
displayed_columns |= ColumnData::Flushed;
|
|
flushed_filaments_m_all_plates.push_back(flushed_filament_m);
|
|
flushed_filaments_g_all_plates.push_back(flushed_filament_g);
|
|
}
|
|
for (auto it = wipe_tower_volume_of_extruders_all_plates.begin(); it != wipe_tower_volume_of_extruders_all_plates.end(); it++) {
|
|
auto [wipe_tower_filament_m, wipe_tower_filament_g] = get_used_filament_from_volume(it->second, it->first);
|
|
if (wipe_tower_filament_m != 0.0 || wipe_tower_filament_g != 0.0)
|
|
displayed_columns |= ColumnData::WipeTower;
|
|
wipe_tower_used_filaments_m_all_plates.push_back(wipe_tower_filament_m);
|
|
wipe_tower_used_filaments_g_all_plates.push_back(wipe_tower_filament_g);
|
|
}
|
|
for (auto it = support_volume_of_extruders_all_plates.begin(); it != support_volume_of_extruders_all_plates.end(); it++) {
|
|
auto [support_filament_m, support_filament_g] = get_used_filament_from_volume(it->second, it->first);
|
|
if (support_filament_m != 0.0 || support_filament_g != 0.0)
|
|
displayed_columns |= ColumnData::Support;
|
|
support_used_filaments_m_all_plates.push_back(support_filament_m);
|
|
support_used_filaments_g_all_plates.push_back(support_filament_g);
|
|
}
|
|
|
|
char buff[64];
|
|
double longest_str = 0.0;
|
|
for (auto i : model_used_filaments_g_all_plates) {
|
|
if (i > longest_str)
|
|
longest_str = i;
|
|
}
|
|
::sprintf(buff, "%.2f", longest_str);
|
|
|
|
std::vector<std::pair<std::string, std::vector<::string>>> title_columns;
|
|
if (displayed_columns & ColumnData::Model) {
|
|
title_columns.push_back({ _u8L("Filament"), {""} });
|
|
title_columns.push_back({ _u8L("Model"), {buff} });
|
|
}
|
|
if (displayed_columns & ColumnData::Support) {
|
|
title_columns.push_back({ _u8L("Support"), {buff} });
|
|
}
|
|
if (displayed_columns & ColumnData::Flushed) {
|
|
title_columns.push_back({ _u8L("Flushed"), {buff} });
|
|
}
|
|
if (displayed_columns & ColumnData::WipeTower) {
|
|
title_columns.push_back({ _u8L("Tower"), {buff} });
|
|
}
|
|
if ((displayed_columns & ~ColumnData::Model) > 0) {
|
|
title_columns.push_back({ _u8L("Total"), {buff} });
|
|
}
|
|
auto offsets_ = calculate_offsets(title_columns, icon_size);
|
|
std::vector<std::pair<std::string, float>> title_offsets;
|
|
for (int i = 0; i < offsets_.size(); i++) {
|
|
title_offsets.push_back({ title_columns[i].first, offsets_[i] });
|
|
offsets[title_columns[i].first] = offsets_[i];
|
|
}
|
|
append_headers(title_offsets);
|
|
}
|
|
|
|
// item
|
|
{
|
|
size_t i = 0;
|
|
for (auto it = model_volume_of_extruders_all_plates.begin(); it != model_volume_of_extruders_all_plates.end(); it++) {
|
|
if (i < model_used_filaments_m_all_plates.size() && i < model_used_filaments_g_all_plates.size()) {
|
|
std::vector<std::pair<std::string, float>> columns_offsets;
|
|
columns_offsets.push_back({ std::to_string(it->first + 1), offsets[_u8L("Filament")]});
|
|
|
|
char buf[64];
|
|
|
|
float column_sum_m = 0.0f;
|
|
float column_sum_g = 0.0f;
|
|
if (displayed_columns & ColumnData::Model) {
|
|
if ((displayed_columns & ~ColumnData::Model) > 0)
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] / unit_conver);
|
|
else
|
|
::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] / unit_conver);
|
|
columns_offsets.push_back({ buf, offsets[_u8L("Model")] });
|
|
column_sum_m += model_used_filaments_m_all_plates[i];
|
|
column_sum_g += model_used_filaments_g_all_plates[i];
|
|
}
|
|
if (displayed_columns & ColumnData::Support) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m_all_plates[i], support_used_filaments_g_all_plates[i] / unit_conver);
|
|
columns_offsets.push_back({ buf, offsets[_u8L("Support")] });
|
|
column_sum_m += support_used_filaments_m_all_plates[i];
|
|
column_sum_g += support_used_filaments_g_all_plates[i];
|
|
}
|
|
if (displayed_columns & ColumnData::Flushed) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m_all_plates[i], flushed_filaments_g_all_plates[i] / unit_conver);
|
|
columns_offsets.push_back({ buf, offsets[_u8L("Flushed")] });
|
|
column_sum_m += flushed_filaments_m_all_plates[i];
|
|
column_sum_g += flushed_filaments_g_all_plates[i];
|
|
}
|
|
if (displayed_columns & ColumnData::WipeTower) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m_all_plates[i], wipe_tower_used_filaments_g_all_plates[i] / unit_conver);
|
|
columns_offsets.push_back({ buf, offsets[_u8L("Tower")] });
|
|
column_sum_m += wipe_tower_used_filaments_m_all_plates[i];
|
|
column_sum_g += wipe_tower_used_filaments_g_all_plates[i];
|
|
}
|
|
if ((displayed_columns & ~ColumnData::Model) > 0) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver);
|
|
columns_offsets.push_back({ buf, offsets[_u8L("Total")] });
|
|
}
|
|
|
|
append_item(true, filament_colors[it->first], columns_offsets);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
// Sum of all rows
|
|
char buf[64];
|
|
if (model_volume_of_extruders_all_plates.size() > 1) {
|
|
// Separator
|
|
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
|
const ImRect separator(ImVec2(window->Pos.x + window_padding * 3, window->DC.CursorPos.y),
|
|
ImVec2(window->Pos.x + window->Size.x - window_padding * 3, window->DC.CursorPos.y + 1.0f));
|
|
ImGui::ItemSize(ImVec2(0.0f, 0.0f));
|
|
const bool item_visible = ImGui::ItemAdd(separator, 0);
|
|
window->DrawList->AddLine(separator.Min, ImVec2(separator.Max.x, separator.Min.y), ImGui::GetColorU32(ImGuiCol_Separator));
|
|
|
|
std::vector<std::pair<std::string, float>> columns_offsets;
|
|
columns_offsets.push_back({_u8L("Total"), offsets[_u8L("Filament")]});
|
|
double total_model_used_filament_m = 0;
|
|
double total_model_used_filament_g = 0;
|
|
double total_support_used_filament_m = 0;
|
|
double total_support_used_filament_g = 0;
|
|
double total_flushed_filament_m = 0;
|
|
double total_flushed_filament_g = 0;
|
|
double total_wipe_tower_used_filament_m = 0;
|
|
double total_wipe_tower_used_filament_g = 0;
|
|
if (displayed_columns & ColumnData::Model) {
|
|
std::for_each(model_used_filaments_m_all_plates.begin(), model_used_filaments_m_all_plates.end(), [&total_model_used_filament_m](double value) {
|
|
total_model_used_filament_m += value;
|
|
});
|
|
std::for_each(model_used_filaments_g_all_plates.begin(), model_used_filaments_g_all_plates.end(), [&total_model_used_filament_g](double value) {
|
|
total_model_used_filament_g += value;
|
|
});
|
|
if ((displayed_columns & ~ColumnData::Model) > 0)
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver);
|
|
else
|
|
::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver);
|
|
columns_offsets.push_back({buf, offsets[_u8L("Model")]});
|
|
}
|
|
if (displayed_columns & ColumnData::Support) {
|
|
std::for_each(model_used_filaments_m_all_plates.begin(), model_used_filaments_m_all_plates.end(), [&total_support_used_filament_m](double value) {
|
|
total_support_used_filament_m += value;
|
|
});
|
|
std::for_each(model_used_filaments_g_all_plates.begin(), model_used_filaments_g_all_plates.end(), [&total_support_used_filament_g](double value) {
|
|
total_support_used_filament_g += value;
|
|
});
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_support_used_filament_m, total_support_used_filament_g / unit_conver);
|
|
columns_offsets.push_back({buf, offsets[_u8L("Support")]});
|
|
}
|
|
if (displayed_columns & ColumnData::Flushed) {
|
|
std::for_each(flushed_filaments_m_all_plates.begin(), flushed_filaments_m_all_plates.end(), [&total_flushed_filament_m](double value) {
|
|
total_flushed_filament_m += value;
|
|
});
|
|
std::for_each(flushed_filaments_g_all_plates.begin(), flushed_filaments_g_all_plates.end(), [&total_flushed_filament_g](double value) {
|
|
total_flushed_filament_g += value;
|
|
});
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_flushed_filament_m, total_flushed_filament_g / unit_conver);
|
|
columns_offsets.push_back({buf, offsets[_u8L("Flushed")]});
|
|
}
|
|
if (displayed_columns & ColumnData::WipeTower) {
|
|
std::for_each(wipe_tower_used_filaments_m_all_plates.begin(), wipe_tower_used_filaments_m_all_plates.end(), [&total_wipe_tower_used_filament_m](double value) {
|
|
total_wipe_tower_used_filament_m += value;
|
|
});
|
|
std::for_each(wipe_tower_used_filaments_g_all_plates.begin(), wipe_tower_used_filaments_g_all_plates.end(), [&total_wipe_tower_used_filament_g](double value) {
|
|
total_wipe_tower_used_filament_g += value;
|
|
});
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_wipe_tower_used_filament_m, total_wipe_tower_used_filament_g / unit_conver);
|
|
columns_offsets.push_back({buf, offsets[_u8L("Tower")]});
|
|
}
|
|
if ((displayed_columns & ~ColumnData::Model) > 0) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g",
|
|
total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m,
|
|
(total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g) / unit_conver);
|
|
columns_offsets.push_back({buf, offsets[_u8L("Total")]});
|
|
}
|
|
append_item(false, m_tools.m_tool_colors[0], columns_offsets);
|
|
}
|
|
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(_u8L("Total cost") + ":");
|
|
ImGui::SameLine();
|
|
::sprintf(buf, "%.2f", total_cost_all_plates);
|
|
imgui.text(buf);
|
|
|
|
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.title(_u8L("Time Estimation"));
|
|
|
|
for (auto it = plate_time.begin(); it != plate_time.end(); it++) {
|
|
std::vector<std::pair<std::string, float>> columns_offsets;
|
|
columns_offsets.push_back({ _u8L("Plate") + " " + std::to_string(it->first), offsets[_u8L("Filament")]});
|
|
columns_offsets.push_back({ short_time(get_time_dhms(it->second)), offsets[_u8L("Model")] });
|
|
append_item(false, m_tools.m_tool_colors[0], columns_offsets);
|
|
}
|
|
|
|
if (plate_time.size() > 1) {
|
|
// Separator
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
const ImRect separator(ImVec2(window->Pos.x + window_padding * 3, window->DC.CursorPos.y),
|
|
ImVec2(window->Pos.x + window->Size.x - window_padding * 3, window->DC.CursorPos.y + 1.0f));
|
|
ImGui::ItemSize(ImVec2(0.0f, 0.0f));
|
|
const bool item_visible = ImGui::ItemAdd(separator, 0);
|
|
window->DrawList->AddLine(separator.Min, ImVec2(separator.Max.x, separator.Min.y), ImGui::GetColorU32(ImGuiCol_Separator));
|
|
std::vector<std::pair<std::string, float>> columns_offsets;
|
|
columns_offsets.push_back({ _u8L("Total"), offsets[_u8L("Filament")] });
|
|
columns_offsets.push_back({ short_time(get_time_dhms(total_time_all_plates)), offsets[_u8L("Model")] });
|
|
append_item(false, m_tools.m_tool_colors[0], columns_offsets);
|
|
}
|
|
}
|
|
ImGui::End();
|
|
ImGui::PopStyleColor(6);
|
|
ImGui::PopStyleVar(3);
|
|
return;
|
|
}
|
|
|
|
void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canvas_height, int right_margin)
|
|
{
|
|
if (!m_legend_enabled)
|
|
return;
|
|
|
|
const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
|
|
|
|
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::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0,0.0));
|
|
ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f,1.0f,1.0f,0.6f));
|
|
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
|
|
ImGui::SetNextWindowBgAlpha(0.8f);
|
|
const float max_height = 0.75f * static_cast<float>(cnv_size.get_height());
|
|
const float child_height = 0.3333f * max_height;
|
|
ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height });
|
|
imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove);
|
|
|
|
enum class EItemType : unsigned char
|
|
{
|
|
Rect,
|
|
Circle,
|
|
Hexagon,
|
|
Line,
|
|
None
|
|
};
|
|
|
|
const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast<size_t>(m_time_estimate_mode)];
|
|
//BBS
|
|
/*bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType ||
|
|
(m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()));*/
|
|
bool show_estimated = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || m_view_type == EViewType::ColorPrint);
|
|
|
|
const float icon_size = ImGui::GetTextLineHeight() * 0.7;
|
|
//BBS GUI refactor
|
|
//const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight();
|
|
const float percent_bar_size = 0;
|
|
|
|
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
ImVec2 pos_rect = ImGui::GetCursorScreenPos();
|
|
float window_padding = 4.0f * m_scale;
|
|
float checkbox_offset = 0.0f;
|
|
|
|
draw_list->AddRectFilled(ImVec2(pos_rect.x,pos_rect.y - ImGui::GetStyle().WindowPadding.y),
|
|
ImVec2(pos_rect.x + ImGui::GetWindowWidth() + ImGui::GetFrameHeight(),pos_rect.y + ImGui::GetFrameHeight() + window_padding * 2.5),
|
|
ImGui::GetColorU32(ImVec4(0,0,0,0.3)));
|
|
|
|
auto append_item = [icon_size, &imgui, imperial_units, &window_padding, &draw_list, &checkbox_offset, this](
|
|
EItemType type,
|
|
const Color& color,
|
|
const std::vector<std::pair<std::string, float>>& columns_offsets,
|
|
bool checkbox = true,
|
|
bool visible = true,
|
|
std::function<void()> callback = nullptr)
|
|
{
|
|
// render icon
|
|
ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y);
|
|
switch (type) {
|
|
default:
|
|
case EItemType::Rect: {
|
|
draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale },
|
|
ImGui::GetColorU32({color[0], color[1], color[2], color[3]}));
|
|
break;
|
|
}
|
|
case EItemType::Circle: {
|
|
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size + 5.0f));
|
|
draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({color[0], color[1], color[2], color[3]}), 16);
|
|
break;
|
|
}
|
|
case EItemType::Hexagon: {
|
|
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size + 5.0f));
|
|
draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({color[0], color[1], color[2], color[3]}), 6);
|
|
break;
|
|
}
|
|
case EItemType::Line: {
|
|
draw_list->AddLine({pos.x + 1, pos.y + icon_size + 2}, {pos.x + icon_size - 1, pos.y + 4}, ImGui::GetColorU32({color[0], color[1], color[2], color[3]}), 3.0f);
|
|
break;
|
|
case EItemType::None:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0 * m_scale, 6.0 * m_scale));
|
|
|
|
// BBS render selectable
|
|
ImGui::Dummy({ 0.0, 0.0 });
|
|
ImGui::SameLine();
|
|
if (callback) {
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * m_scale);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0 * m_scale, 0.0));
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.00f, 0.68f, 0.26f, 0.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(1.00f, 0.68f, 0.26f, 0.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
|
|
float max_height = 0.f;
|
|
for (auto column_offset : columns_offsets) {
|
|
if (ImGui::CalcTextSize(column_offset.first.c_str()).y > max_height)
|
|
max_height = ImGui::CalcTextSize(column_offset.first.c_str()).y;
|
|
}
|
|
bool b_menu_item = ImGui::BBLMenuItem(("##" + columns_offsets[0].first).c_str(), nullptr, false, true, max_height);
|
|
ImGui::PopStyleVar(2);
|
|
ImGui::PopStyleColor(3);
|
|
if (b_menu_item)
|
|
callback();
|
|
if (checkbox) {
|
|
ImGui::SameLine(checkbox_offset);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0, 0.0));
|
|
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
|
|
ImGui::Checkbox(("##" + columns_offsets[0].first).c_str(), &visible);
|
|
ImGui::PopStyleColor(1);
|
|
ImGui::PopStyleVar(1);
|
|
}
|
|
}
|
|
|
|
// BBS render column item
|
|
{
|
|
if(callback && !checkbox && !visible)
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(172 / 255.0f, 172 / 255.0f, 172 / 255.0f, 1.00f));
|
|
float dummy_size = type == EItemType::None ? window_padding * 3 : ImGui::GetStyle().ItemSpacing.x + icon_size;
|
|
ImGui::SameLine(dummy_size);
|
|
imgui.text(columns_offsets[0].first);
|
|
|
|
for (auto i = 1; i < columns_offsets.size(); i++) {
|
|
ImGui::SameLine(columns_offsets[i].second);
|
|
imgui.text(columns_offsets[i].first);
|
|
}
|
|
if (callback && !checkbox && !visible)
|
|
ImGui::PopStyleColor(1);
|
|
}
|
|
|
|
ImGui::PopStyleVar(1);
|
|
|
|
};
|
|
|
|
auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) {
|
|
auto append_range_item = [append_item](int i, float value, unsigned int decimals) {
|
|
char buf[1024];
|
|
::sprintf(buf, "%.*f", decimals, value);
|
|
append_item(EItemType::Rect, Range_Colors[i], { { buf , 0} });
|
|
};
|
|
|
|
if (range.count == 1)
|
|
// single item use case
|
|
append_range_item(0, range.min, decimals);
|
|
else if (range.count == 2) {
|
|
append_range_item(static_cast<int>(Range_Colors.size()) - 1, range.max, decimals);
|
|
append_range_item(0, range.min, decimals);
|
|
}
|
|
else {
|
|
const float step_size = range.step_size();
|
|
for (int i = static_cast<int>(Range_Colors.size()) - 1; i >= 0; --i) {
|
|
append_range_item(i, range.get_value_at_step(i), decimals);
|
|
}
|
|
}
|
|
};
|
|
|
|
auto append_headers = [&imgui, &window_padding](const std::vector<std::pair<std::string, float>>& title_offsets) {
|
|
for (size_t i = 0; i < title_offsets.size(); i++) {
|
|
ImGui::SameLine(title_offsets[i].second);
|
|
imgui.bold_text(title_offsets[i].first);
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::Dummy({ window_padding, 0 });
|
|
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, window_padding, &checkbox_offset](const std::vector<std::pair<std::string, std::vector<::string>>>& title_columns, float extra_size = 0.0f) {
|
|
const ImGuiStyle& style = ImGui::GetStyle();
|
|
std::vector<float> offsets;
|
|
offsets.push_back(max_width(title_columns[0].second, title_columns[0].first, extra_size) + 3.0f * style.ItemSpacing.x);
|
|
for (size_t i = 2; i < title_columns.size(); i++) {
|
|
if (title_columns[i].first == "") {
|
|
offsets.push_back(offsets.back() + max_width(title_columns[i - 1].second, "") + style.ItemSpacing.x);
|
|
}
|
|
else if (title_columns[i].first == _u8L("Display")) {
|
|
float length = ImGui::CalcTextSize(title_columns[i - 2].first.c_str()).x;
|
|
float offset = offsets.back() + max_width(title_columns[i - 1].second, title_columns[i - 1].first);
|
|
size_t index = offsets.size() - 2;
|
|
if (index >= 0) {
|
|
offset = std::max(offset, length + offsets[index]);
|
|
}
|
|
offsets.push_back(offset + 2.0f * style.ItemSpacing.x);
|
|
}
|
|
else {
|
|
offsets.push_back(offsets.back() + max_width(title_columns[i - 1].second, title_columns[i - 1].first) + 2.0f * style.ItemSpacing.x);
|
|
}
|
|
}
|
|
|
|
float average_col_width = ImGui::GetWindowWidth() / static_cast<float>(title_columns.size());
|
|
std::vector<float> ret;
|
|
ret.push_back(0);
|
|
for (size_t i = 1; i < title_columns.size(); i++) {
|
|
ret.push_back(std::max(offsets[i - 1], i * average_col_width));
|
|
}
|
|
|
|
if (title_columns.back().first == _u8L("Display")) {
|
|
checkbox_offset = ret.back() + window_padding;
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
// BBS: no ColorChange type, use ToolChange
|
|
//auto color_print_ranges = [this](unsigned char extruder_id, const std::vector<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 move_time_and_percent = [time_mode](EMoveType move_type) {
|
|
auto it = std::find_if(time_mode.moves_times.begin(), time_mode.moves_times.end(), [move_type](const std::pair<EMoveType, float>& item) { return move_type == item.first; });
|
|
return (it != time_mode.moves_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f);
|
|
};
|
|
|
|
auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) {
|
|
auto it = m_print_statistics.used_filaments_per_role.find(role);
|
|
if (it == m_print_statistics.used_filaments_per_role.end())
|
|
return std::make_pair(0.0, 0.0);
|
|
|
|
double koef = imperial_units ? 1000.0 / GizmoObjectManipulation::in_to_mm : 1.0;
|
|
return std::make_pair(it->second.first * koef, it->second.second);
|
|
};
|
|
|
|
// get used filament (meters and grams) from used volume in respect to the active extruder
|
|
auto get_used_filament_from_volume = [this, imperial_units](double volume, int extruder_id) {
|
|
double koef = imperial_units ? 1.0 / GizmoObjectManipulation::in_to_mm : 0.001;
|
|
std::pair<double, double> ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])),
|
|
volume * m_filament_densities[extruder_id] * 0.001 };
|
|
return ret;
|
|
};
|
|
|
|
//BBS display Color Scheme
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
std::wstring btn_name;
|
|
if (m_fold)
|
|
btn_name = ImGui::UnfoldButtonIcon + boost::nowide::widen(std::string(""));
|
|
else
|
|
btn_name = ImGui::FoldButtonIcon + boost::nowide::widen(std::string(""));
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.68f, 0.26f, 1.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.0f, 0.68f, 0.26f, 0.78f));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
|
//ImGui::PushItemWidth(
|
|
float button_width = ImGui::CalcTextSize(into_u8(btn_name).c_str()).x;
|
|
if (ImGui::Button(into_u8(btn_name).c_str(), ImVec2(button_width, 0))) {
|
|
m_fold = !m_fold;
|
|
}
|
|
ImGui::PopStyleColor(3);
|
|
ImGui::PopStyleVar(1);
|
|
ImGui::SameLine();
|
|
imgui.bold_text(_u8L("Color Scheme"));
|
|
push_combo_style();
|
|
|
|
ImGui::SameLine();
|
|
const char* view_type_value = view_type_items_str[m_view_type_sel].c_str();
|
|
ImGuiComboFlags flags = 0;
|
|
if (ImGui::BBLBeginCombo("", view_type_value, flags)) {
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
|
|
for (int i = 0; i < view_type_items_str.size(); i++) {
|
|
const bool is_selected = (m_view_type_sel == i);
|
|
if (ImGui::BBLSelectable(view_type_items_str[i].c_str(), is_selected)) {
|
|
m_fold = false;
|
|
m_view_type_sel = i;
|
|
set_view_type(view_type_items[m_view_type_sel]);
|
|
reset_visible(view_type_items[m_view_type_sel]);
|
|
// update buffers' render paths
|
|
refresh_render_paths(false, false);
|
|
update_moves_slider();
|
|
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
|
}
|
|
if (is_selected) {
|
|
ImGui::SetItemDefaultFocus();
|
|
}
|
|
}
|
|
ImGui::PopStyleVar(1);
|
|
ImGui::EndCombo();
|
|
}
|
|
pop_combo_style();
|
|
ImGui::SameLine();
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
|
|
if (m_fold) {
|
|
legend_height = ImGui::GetStyle().WindowPadding.y + ImGui::GetFrameHeight() + window_padding * 2.5;
|
|
imgui.end();
|
|
ImGui::PopStyleColor(6);
|
|
ImGui::PopStyleVar(2);
|
|
return;
|
|
}
|
|
|
|
// data used to properly align items in columns when showing time
|
|
std::vector<float> offsets;
|
|
std::vector<std::string> labels;
|
|
std::vector<std::string> times;
|
|
std::string travel_time;
|
|
std::vector<std::string> percents;
|
|
std::string travel_percent;
|
|
std::vector<double> model_used_filaments_m;
|
|
std::vector<double> model_used_filaments_g;
|
|
std::vector<std::string> used_filaments_m;
|
|
std::vector<std::string> used_filaments_g;
|
|
double total_model_used_filament_m = 0, total_model_used_filament_g = 0;
|
|
std::vector<double> flushed_filaments_m;
|
|
std::vector<double> flushed_filaments_g;
|
|
double total_flushed_filament_m = 0, total_flushed_filament_g = 0;
|
|
std::vector<double> wipe_tower_used_filaments_m;
|
|
std::vector<double> wipe_tower_used_filaments_g;
|
|
double total_wipe_tower_used_filament_m = 0, total_wipe_tower_used_filament_g = 0;
|
|
std::vector<double> support_used_filaments_m;
|
|
std::vector<double> support_used_filaments_g;
|
|
double total_support_used_filament_m = 0, total_support_used_filament_g = 0;
|
|
struct ColumnData {
|
|
enum {
|
|
Model = 1,
|
|
Flushed = 2,
|
|
WipeTower = 4,
|
|
Support = 1 << 3,
|
|
};
|
|
};
|
|
int displayed_columns = 0;
|
|
std::map<std::string, float> color_print_offsets;
|
|
const PrintStatistics& ps = wxGetApp().plater()->get_partplate_list().get_current_fff_print().print_statistics();
|
|
double koef = imperial_units ? GizmoObjectManipulation::in_to_mm : 1000.0;
|
|
double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1;
|
|
|
|
|
|
// used filament statistics
|
|
for (size_t extruder_id : m_extruder_ids) {
|
|
if (m_print_statistics.model_volumes_per_extruder.find(extruder_id) == m_print_statistics.model_volumes_per_extruder.end()) {
|
|
model_used_filaments_m.push_back(0.0);
|
|
model_used_filaments_g.push_back(0.0);
|
|
}
|
|
else {
|
|
double volume = m_print_statistics.model_volumes_per_extruder.at(extruder_id);
|
|
auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
|
|
model_used_filaments_m.push_back(model_used_filament_m);
|
|
model_used_filaments_g.push_back(model_used_filament_g);
|
|
total_model_used_filament_m += model_used_filament_m;
|
|
total_model_used_filament_g += model_used_filament_g;
|
|
displayed_columns |= ColumnData::Model;
|
|
}
|
|
}
|
|
|
|
for (size_t extruder_id : m_extruder_ids) {
|
|
if (m_print_statistics.wipe_tower_volumes_per_extruder.find(extruder_id) == m_print_statistics.wipe_tower_volumes_per_extruder.end()) {
|
|
wipe_tower_used_filaments_m.push_back(0.0);
|
|
wipe_tower_used_filaments_g.push_back(0.0);
|
|
}
|
|
else {
|
|
double volume = m_print_statistics.wipe_tower_volumes_per_extruder.at(extruder_id);
|
|
auto [wipe_tower_used_filament_m, wipe_tower_used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
|
|
wipe_tower_used_filaments_m.push_back(wipe_tower_used_filament_m);
|
|
wipe_tower_used_filaments_g.push_back(wipe_tower_used_filament_g);
|
|
total_wipe_tower_used_filament_m += wipe_tower_used_filament_m;
|
|
total_wipe_tower_used_filament_g += wipe_tower_used_filament_g;
|
|
displayed_columns |= ColumnData::WipeTower;
|
|
}
|
|
}
|
|
|
|
for (size_t extruder_id : m_extruder_ids) {
|
|
if (m_print_statistics.flush_per_filament.find(extruder_id) == m_print_statistics.flush_per_filament.end()) {
|
|
flushed_filaments_m.push_back(0.0);
|
|
flushed_filaments_g.push_back(0.0);
|
|
}
|
|
else {
|
|
double volume = m_print_statistics.flush_per_filament.at(extruder_id);
|
|
auto [flushed_filament_m, flushed_filament_g] = get_used_filament_from_volume(volume, extruder_id);
|
|
flushed_filaments_m.push_back(flushed_filament_m);
|
|
flushed_filaments_g.push_back(flushed_filament_g);
|
|
total_flushed_filament_m += flushed_filament_m;
|
|
total_flushed_filament_g += flushed_filament_g;
|
|
displayed_columns |= ColumnData::Flushed;
|
|
}
|
|
}
|
|
|
|
for (size_t extruder_id : m_extruder_ids) {
|
|
if (m_print_statistics.support_volumes_per_extruder.find(extruder_id) == m_print_statistics.support_volumes_per_extruder.end()) {
|
|
support_used_filaments_m.push_back(0.0);
|
|
support_used_filaments_g.push_back(0.0);
|
|
}
|
|
else {
|
|
double volume = m_print_statistics.support_volumes_per_extruder.at(extruder_id);
|
|
auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
|
|
support_used_filaments_m.push_back(used_filament_m);
|
|
support_used_filaments_g.push_back(used_filament_g);
|
|
total_support_used_filament_m += used_filament_m;
|
|
total_support_used_filament_g += used_filament_g;
|
|
displayed_columns |= ColumnData::Support;
|
|
}
|
|
}
|
|
|
|
|
|
// extrusion paths section -> title
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
switch (m_view_type)
|
|
{
|
|
case EViewType::FeatureType:
|
|
{
|
|
// calculate offsets to align time/percentage data
|
|
char buffer[64];
|
|
for (size_t i = 0; i < m_roles.size(); ++i) {
|
|
ExtrusionRole role = m_roles[i];
|
|
if (role < erCount) {
|
|
labels.push_back(_u8L(ExtrusionEntity::role_to_string(role)));
|
|
auto [time, percent] = role_time_and_percent(role);
|
|
times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : "");
|
|
if (percent == 0)
|
|
::sprintf(buffer, "0%%");
|
|
else
|
|
percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%");
|
|
percents.push_back(buffer);
|
|
auto [model_used_filament_m, model_used_filament_g] = used_filament_per_role(role);
|
|
//model_used_filaments_m.push_back(model_used_filament_m);
|
|
//model_used_filaments_g.push_back(model_used_filament_g);
|
|
memset(&buffer, 0, sizeof(buffer));
|
|
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", model_used_filament_m);
|
|
used_filaments_m.push_back(buffer);
|
|
|
|
memset(&buffer, 0, sizeof(buffer));
|
|
::sprintf(buffer, "%.2f g", model_used_filament_g);
|
|
used_filaments_g.push_back(buffer);
|
|
}
|
|
}
|
|
|
|
//BBS: get travel time and percent
|
|
{
|
|
auto [time, percent] = move_time_and_percent(EMoveType::Travel);
|
|
travel_time = (time > 0.0f) ? short_time(get_time_dhms(time)) : "";
|
|
if (percent == 0)
|
|
::sprintf(buffer, "0%%");
|
|
else
|
|
percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%");
|
|
travel_percent = buffer;
|
|
}
|
|
|
|
offsets = calculate_offsets({ {_u8L("Line Type"), labels}, {_u8L("Time"), times}, {_u8L("Percent"), percents}, {_u8L("Used filament"), used_filaments_m}, {"", used_filaments_g}, {_u8L("Display"), {""}}}, icon_size);
|
|
append_headers({{_u8L("Line Type"), offsets[0]}, {_u8L("Time"), offsets[1]}, {_u8L("Percent"), offsets[2]}, {_u8L("Used filament"), offsets[3]}, {"", offsets[4]}, {_u8L("Display"), offsets[5]}});
|
|
break;
|
|
}
|
|
case EViewType::Height: { imgui.title(_u8L("Layer Height (mm)")); break; }
|
|
case EViewType::Width: { imgui.title(_u8L("Line Width (mm)")); break; }
|
|
case EViewType::Feedrate:
|
|
{
|
|
imgui.title(_u8L("Speed (mm/s)"));
|
|
break;
|
|
}
|
|
|
|
case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; }
|
|
case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; }
|
|
case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; }
|
|
case EViewType::LayerTime: { imgui.title(_u8L("Layer Time (s)")); break; }
|
|
|
|
case EViewType::Tool:
|
|
{
|
|
// calculate used filaments data
|
|
for (size_t extruder_id : m_extruder_ids) {
|
|
if (m_print_statistics.model_volumes_per_extruder.find(extruder_id) == m_print_statistics.model_volumes_per_extruder.end())
|
|
continue;
|
|
double volume = m_print_statistics.model_volumes_per_extruder.at(extruder_id);
|
|
|
|
auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
|
|
model_used_filaments_m.push_back(model_used_filament_m);
|
|
model_used_filaments_g.push_back(model_used_filament_g);
|
|
}
|
|
|
|
offsets = calculate_offsets({ { "Extruder NNN", {""}}}, icon_size);
|
|
append_headers({ {_u8L("Filament"), offsets[0]}, {_u8L("Used filament"), offsets[1]} });
|
|
break;
|
|
}
|
|
case EViewType::ColorPrint:
|
|
{
|
|
std::vector<std::string> total_filaments;
|
|
char buffer[64];
|
|
::sprintf(buffer, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", ps.total_used_filament / /*1000*/koef, ps.total_weight / unit_conver);
|
|
total_filaments.push_back(buffer);
|
|
|
|
|
|
std::vector<std::pair<std::string, std::vector<::string>>> title_columns;
|
|
if (displayed_columns & ColumnData::Model) {
|
|
title_columns.push_back({ _u8L("Filament"), {""} });
|
|
title_columns.push_back({ _u8L("Model"), total_filaments });
|
|
}
|
|
if (displayed_columns & ColumnData::Support) {
|
|
title_columns.push_back({ _u8L("Support"), total_filaments });
|
|
}
|
|
if (displayed_columns & ColumnData::Flushed) {
|
|
title_columns.push_back({ _u8L("Flushed"), total_filaments });
|
|
}
|
|
if (displayed_columns & ColumnData::WipeTower) {
|
|
title_columns.push_back({ _u8L("Tower"), total_filaments });
|
|
}
|
|
if ((displayed_columns & ~ColumnData::Model) > 0) {
|
|
title_columns.push_back({ _u8L("Total"), total_filaments });
|
|
}
|
|
auto offsets_ = calculate_offsets(title_columns, icon_size);
|
|
std::vector<std::pair<std::string, float>> title_offsets;
|
|
for (int i = 0; i < offsets_.size(); i++) {
|
|
title_offsets.push_back({ title_columns[i].first, offsets_[i] });
|
|
color_print_offsets[title_columns[i].first] = offsets_[i];
|
|
}
|
|
append_headers(title_offsets);
|
|
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
|
|
auto append_option_item = [this, append_item](EMoveType type, std::vector<float> offsets) {
|
|
auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const Color& color, const std::string& label, bool visible) {
|
|
append_item(EItemType::Rect, color, {{ label , offsets[0] }}, true, visible, [this, type, visible]() {
|
|
m_buffers[buffer_id(type)].visible = !m_buffers[buffer_id(type)].visible;
|
|
// update buffers' render paths
|
|
refresh_render_paths(false, false);
|
|
update_moves_slider();
|
|
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
|
});
|
|
};
|
|
const bool visible = m_buffers[buffer_id(type)].visible;
|
|
if (type == EMoveType::Travel) {
|
|
//BBS: only display travel time in FeatureType view
|
|
append_option_item_with_type(type, Travel_Colors[0], _u8L("Travel"), visible);
|
|
}
|
|
else if (type == EMoveType::Seam)
|
|
append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Seams], _u8L("Seams"), visible);
|
|
else if (type == EMoveType::Retract)
|
|
append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Retractions], _u8L("Retract"), visible);
|
|
else if (type == EMoveType::Unretract)
|
|
append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Unretractions], _u8L("Unretract"), visible);
|
|
else if (type == EMoveType::Tool_change)
|
|
append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::ToolChanges], _u8L("Filament Changes"), visible);
|
|
else if (type == EMoveType::Wipe)
|
|
append_option_item_with_type(type, Wipe_Color, _u8L("Wipe"), visible);
|
|
};
|
|
|
|
// extrusion paths section -> items
|
|
switch (m_view_type)
|
|
{
|
|
case EViewType::FeatureType:
|
|
{
|
|
for (size_t i = 0; i < m_roles.size(); ++i) {
|
|
ExtrusionRole role = m_roles[i];
|
|
if (role >= erCount)
|
|
continue;
|
|
const bool visible = is_visible(role);
|
|
std::vector<std::pair<std::string, float>> columns_offsets;
|
|
columns_offsets.push_back({ labels[i], offsets[0] });
|
|
columns_offsets.push_back({ times[i], offsets[1] });
|
|
columns_offsets.push_back({ percents[i], offsets[2] });
|
|
columns_offsets.push_back({ used_filaments_m[i], offsets[3] });
|
|
columns_offsets.push_back({ used_filaments_g[i], offsets[4] });
|
|
append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast<unsigned int>(role)], columns_offsets,
|
|
true, visible, [this, role, visible]() {
|
|
m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role);
|
|
// update buffers' render paths
|
|
refresh_render_paths(false, false);
|
|
update_moves_slider();
|
|
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
|
});
|
|
}
|
|
|
|
for(auto item : options_items) {
|
|
if (item != EMoveType::Travel) {
|
|
append_option_item(item, offsets);
|
|
} else {
|
|
//BBS: show travel time in FeatureType view
|
|
const bool visible = m_buffers[buffer_id(item)].visible;
|
|
std::vector<std::pair<std::string, float>> columns_offsets;
|
|
columns_offsets.push_back({ _u8L("Travel"), offsets[0] });
|
|
columns_offsets.push_back({ travel_time, offsets[1] });
|
|
columns_offsets.push_back({ travel_percent, offsets[2] });
|
|
append_item(EItemType::Rect, Travel_Colors[0], columns_offsets, true, visible, [this, item, visible]() {
|
|
m_buffers[buffer_id(item)].visible = !m_buffers[buffer_id(item)].visible;
|
|
// update buffers' render paths
|
|
refresh_render_paths(false, false);
|
|
update_moves_slider();
|
|
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case EViewType::Height: { append_range(m_extrusions.ranges.height, 2); break; }
|
|
case EViewType::Width: { append_range(m_extrusions.ranges.width, 2); break; }
|
|
case EViewType::Feedrate: {
|
|
append_range(m_extrusions.ranges.feedrate, 0);
|
|
ImGui::Spacing();
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
offsets = calculate_offsets({ { _u8L("Options"), { _u8L("Travel")}}, { _u8L("Display"), {""}} }, icon_size);
|
|
append_headers({ {_u8L("Options"), offsets[0] }, { _u8L("Display"), offsets[1]} });
|
|
const bool travel_visible = m_buffers[buffer_id(EMoveType::Travel)].visible;
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 3.0f));
|
|
append_item(EItemType::None, Travel_Colors[0], { {_u8L("travel"), offsets[0] }}, true, travel_visible, [this, travel_visible]() {
|
|
m_buffers[buffer_id(EMoveType::Travel)].visible = !m_buffers[buffer_id(EMoveType::Travel)].visible;
|
|
// update buffers' render paths, and update m_tools.m_tool_colors and m_extrusions.ranges
|
|
refresh(*m_gcode_result, wxGetApp().plater()->get_extruder_colors_from_plater_config(m_gcode_result));
|
|
update_moves_slider();
|
|
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
|
});
|
|
ImGui::PopStyleVar(1);
|
|
break;
|
|
}
|
|
case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; }
|
|
case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; }
|
|
case EViewType::LayerTime: { append_range(m_extrusions.ranges.layer_duration, 1); break; }
|
|
case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 2); break; }
|
|
case EViewType::Tool:
|
|
{
|
|
// shows only extruders actually used
|
|
char buf[64];
|
|
size_t i = 0;
|
|
for (unsigned char extruder_id : m_extruder_ids) {
|
|
::sprintf(buf, imperial_units ? "%.2f in %.2f g" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i]);
|
|
append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_id], { { _u8L("Extruder") + " " + std::to_string(extruder_id + 1), offsets[0]}, {buf, offsets[1]} });
|
|
i++;
|
|
}
|
|
break;
|
|
}
|
|
case EViewType::ColorPrint:
|
|
{
|
|
//BBS: replace model custom gcode with current plate custom gcode
|
|
const std::vector<CustomGCode::Item>& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z;
|
|
size_t total_items = 1;
|
|
// BBS: no ColorChange type, use ToolChange
|
|
//for (size_t extruder_id : m_extruder_ids) {
|
|
// total_items += color_print_ranges(extruder_id, custom_gcode_per_print_z).size();
|
|
//}
|
|
|
|
const bool need_scrollable = static_cast<float>(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height;
|
|
|
|
// add scrollable region, if needed
|
|
if (need_scrollable)
|
|
ImGui::BeginChild("color_prints", { -1.0f, child_height }, false);
|
|
|
|
// shows only extruders actually used
|
|
size_t i = 0;
|
|
for (auto extruder_idx : m_extruder_ids) {
|
|
const bool filament_visible = m_tools.m_tool_visibles[extruder_idx];
|
|
if (i < model_used_filaments_m.size() && i < model_used_filaments_g.size()) {
|
|
std::vector<std::pair<std::string, float>> columns_offsets;
|
|
columns_offsets.push_back({ std::to_string(extruder_idx + 1), color_print_offsets[_u8L("Filament")]});
|
|
|
|
char buf[64];
|
|
float column_sum_m = 0.0f;
|
|
float column_sum_g = 0.0f;
|
|
if (displayed_columns & ColumnData::Model) {
|
|
if ((displayed_columns & ~ColumnData::Model) > 0)
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver);
|
|
else
|
|
::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] });
|
|
column_sum_m += model_used_filaments_m[i];
|
|
column_sum_g += model_used_filaments_g[i];
|
|
}
|
|
if (displayed_columns & ColumnData::Support) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m[i], support_used_filaments_g[i] / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] });
|
|
column_sum_m += support_used_filaments_m[i];
|
|
column_sum_g += support_used_filaments_g[i];
|
|
}
|
|
if (displayed_columns & ColumnData::Flushed) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m[i], flushed_filaments_g[i] / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")]});
|
|
column_sum_m += flushed_filaments_m[i];
|
|
column_sum_g += flushed_filaments_g[i];
|
|
}
|
|
if (displayed_columns & ColumnData::WipeTower) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m[i], wipe_tower_used_filaments_g[i] / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] });
|
|
column_sum_m += wipe_tower_used_filaments_m[i];
|
|
column_sum_g += wipe_tower_used_filaments_g[i];
|
|
}
|
|
if ((displayed_columns & ~ColumnData::Model) > 0) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] });
|
|
}
|
|
|
|
append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_idx], columns_offsets, false, filament_visible, [this, extruder_idx]() {
|
|
m_tools.m_tool_visibles[extruder_idx] = !m_tools.m_tool_visibles[extruder_idx];
|
|
// update buffers' render paths
|
|
refresh_render_paths(false, false);
|
|
update_moves_slider();
|
|
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
|
});
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (need_scrollable)
|
|
ImGui::EndChild();
|
|
|
|
// Sum of all rows
|
|
char buf[64];
|
|
if (m_extruder_ids.size() > 1) {
|
|
// Separator
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
const ImRect separator(ImVec2(window->Pos.x + window_padding * 3, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x - window_padding * 3, window->DC.CursorPos.y + 1.0f));
|
|
ImGui::ItemSize(ImVec2(0.0f, 0.0f));
|
|
const bool item_visible = ImGui::ItemAdd(separator, 0);
|
|
window->DrawList->AddLine(separator.Min, ImVec2(separator.Max.x, separator.Min.y), ImGui::GetColorU32(ImGuiCol_Separator));
|
|
|
|
std::vector<std::pair<std::string, float>> columns_offsets;
|
|
columns_offsets.push_back({ _u8L("Total"), color_print_offsets[_u8L("Filament")]});
|
|
if (displayed_columns & ColumnData::Model) {
|
|
if ((displayed_columns & ~ColumnData::Model) > 0)
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver);
|
|
else
|
|
::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] });
|
|
}
|
|
if (displayed_columns & ColumnData::Support) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_support_used_filament_m, total_support_used_filament_g / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] });
|
|
}
|
|
if (displayed_columns & ColumnData::Flushed) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_flushed_filament_m, total_flushed_filament_g / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")] });
|
|
}
|
|
if (displayed_columns & ColumnData::WipeTower) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_wipe_tower_used_filament_m, total_wipe_tower_used_filament_g / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] });
|
|
}
|
|
if ((displayed_columns & ~ColumnData::Model) > 0) {
|
|
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m,
|
|
(total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g) / unit_conver);
|
|
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] });
|
|
}
|
|
append_item(EItemType::None, m_tools.m_tool_colors[0], columns_offsets);
|
|
}
|
|
|
|
//BBS display filament change times
|
|
ImGui::Dummy({window_padding, window_padding});
|
|
ImGui::SameLine();
|
|
imgui.text(_u8L("Filament change times") + ":");
|
|
ImGui::SameLine();
|
|
::sprintf(buf, "%d", m_print_statistics.total_filamentchanges);
|
|
imgui.text(buf);
|
|
|
|
//BBS display cost
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(_u8L("Cost")+":");
|
|
ImGui::SameLine();
|
|
::sprintf(buf, "%.2f", ps.total_cost);
|
|
imgui.text(buf);
|
|
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
|
|
// partial estimated printing time section
|
|
if (m_view_type == EViewType::ColorPrint) {
|
|
using Times = std::pair<float, float>;
|
|
using TimesList = std::vector<std::pair<CustomGCode::Type, Times>>;
|
|
|
|
// helper structure containig the data needed to render the time items
|
|
struct PartialTime
|
|
{
|
|
enum class EType : unsigned char
|
|
{
|
|
Print,
|
|
ColorChange,
|
|
Pause
|
|
};
|
|
EType type;
|
|
int extruder_id;
|
|
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;
|
|
|
|
//BBS: replace model custom gcode with current plate custom gcode
|
|
std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z;
|
|
std::vector<Color> last_color(m_extruders_count);
|
|
for (size_t i = 0; i < m_extruders_count; ++i) {
|
|
last_color[i] = m_tools.m_tool_colors[i];
|
|
}
|
|
int last_extruder_id = 1;
|
|
int color_change_idx = 0;
|
|
for (const auto& time_rec : times) {
|
|
switch (time_rec.first)
|
|
{
|
|
case CustomGCode::PausePrint: {
|
|
auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
|
|
if (it != custom_gcode_per_print_z.end()) {
|
|
items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second });
|
|
items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second });
|
|
custom_gcode_per_print_z.erase(it);
|
|
}
|
|
break;
|
|
}
|
|
case CustomGCode::ColorChange: {
|
|
auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
|
|
if (it != custom_gcode_per_print_z.end()) {
|
|
items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) });
|
|
items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second });
|
|
last_color[it->extruder - 1] = decode_color(it->color);
|
|
last_extruder_id = it->extruder;
|
|
custom_gcode_per_print_z.erase(it);
|
|
}
|
|
else
|
|
items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) });
|
|
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
}
|
|
|
|
return items;
|
|
};
|
|
|
|
auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array<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::Dummy({ window_padding, window_padding });
|
|
// ImGui::SameLine();
|
|
// imgui.title(_u8L("Wipe"));
|
|
|
|
// // items
|
|
// append_item(EItemType::Line, Wipe_Color, { {_u8L("Wipe"), 0} });
|
|
|
|
// break;
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
auto any_option_available = [this]() {
|
|
auto available = [this](EMoveType type) {
|
|
const TBuffer& buffer = m_buffers[buffer_id(type)];
|
|
return buffer.visible && buffer.has_data();
|
|
};
|
|
|
|
return available(EMoveType::Color_change) ||
|
|
available(EMoveType::Custom_GCode) ||
|
|
available(EMoveType::Pause_Print) ||
|
|
available(EMoveType::Retract) ||
|
|
available(EMoveType::Tool_change) ||
|
|
available(EMoveType::Unretract) ||
|
|
available(EMoveType::Seam);
|
|
};
|
|
|
|
//auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) {
|
|
// const TBuffer& buffer = m_buffers[buffer_id(move_type)];
|
|
// if (buffer.visible && buffer.has_data())
|
|
// append_item(EItemType::Circle, Options_Colors[static_cast<unsigned int>(color)], text);
|
|
//};
|
|
|
|
/* BBS GUI refactor */
|
|
// options section
|
|
//if (any_option_available()) {
|
|
// // title
|
|
// ImGui::Spacing();
|
|
// imgui.title(_u8L("Options"));
|
|
|
|
// // items
|
|
// add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions"));
|
|
// add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions"));
|
|
// add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams"));
|
|
// add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes"));
|
|
// add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes"));
|
|
// add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses"));
|
|
// add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes"));
|
|
//}
|
|
|
|
|
|
// settings section
|
|
bool has_settings = false;
|
|
has_settings |= !m_settings_ids.print.empty();
|
|
has_settings |= !m_settings_ids.printer.empty();
|
|
bool has_filament_settings = true;
|
|
has_filament_settings &= !m_settings_ids.filament.empty();
|
|
for (const std::string& fs : m_settings_ids.filament) {
|
|
has_filament_settings &= !fs.empty();
|
|
}
|
|
has_settings |= has_filament_settings;
|
|
//BBS: add only gcode mode
|
|
bool show_settings = m_only_gcode_in_preview; //wxGetApp().is_gcode_viewer();
|
|
show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool);
|
|
show_settings &= has_settings;
|
|
if (show_settings) {
|
|
auto calc_offset = [this]() {
|
|
float ret = 0.0f;
|
|
if (!m_settings_ids.printer.empty())
|
|
ret = std::max(ret, ImGui::CalcTextSize((_u8L("Printer") + std::string(":")).c_str()).x);
|
|
if (!m_settings_ids.print.empty())
|
|
ret = std::max(ret, ImGui::CalcTextSize((_u8L("Print settings") + std::string(":")).c_str()).x);
|
|
if (!m_settings_ids.filament.empty()) {
|
|
for (unsigned char i : m_extruder_ids) {
|
|
ret = std::max(ret, ImGui::CalcTextSize((_u8L("Filament") + " " + std::to_string(i + 1) + ":").c_str()).x);
|
|
}
|
|
}
|
|
if (ret > 0.0f)
|
|
ret += 2.0f * ImGui::GetStyle().ItemSpacing.x;
|
|
return ret;
|
|
};
|
|
|
|
ImGui::Spacing();
|
|
imgui.title(_u8L("Settings"));
|
|
|
|
float offset = calc_offset();
|
|
|
|
if (!m_settings_ids.printer.empty()) {
|
|
imgui.text(_u8L("Printer") + ":");
|
|
ImGui::SameLine(offset);
|
|
imgui.text(m_settings_ids.printer);
|
|
}
|
|
if (!m_settings_ids.print.empty()) {
|
|
imgui.text(_u8L("Print settings") + ":");
|
|
ImGui::SameLine(offset);
|
|
imgui.text(m_settings_ids.print);
|
|
}
|
|
if (!m_settings_ids.filament.empty()) {
|
|
for (unsigned char i : m_extruder_ids) {
|
|
if (i < static_cast<unsigned char>(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) {
|
|
std::string txt = _u8L("Filament");
|
|
txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1);
|
|
imgui.text(txt);
|
|
ImGui::SameLine(offset);
|
|
imgui.text(m_settings_ids.filament[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// total estimated printing time section
|
|
if (show_estimated) {
|
|
ImGui::Spacing();
|
|
std::string time_title = m_view_type == EViewType::FeatureType ? _u8L("Total Estimation") : _u8L("Time Estimation");
|
|
auto can_show_mode_button = [this](PrintEstimatedStatistics::ETimeMode mode) {
|
|
bool show = false;
|
|
if (m_print_statistics.modes.size() > 1 && m_print_statistics.modes[static_cast<size_t>(mode)].roles_times.size() > 0) {
|
|
for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) {
|
|
if (i != static_cast<size_t>(mode) &&
|
|
m_print_statistics.modes[i].time > 0.0f &&
|
|
short_time(get_time_dhms(m_print_statistics.modes[static_cast<size_t>(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) {
|
|
show = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return show;
|
|
};
|
|
|
|
if (can_show_mode_button(m_time_estimate_mode)) {
|
|
switch (m_time_estimate_mode)
|
|
{
|
|
case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; }
|
|
default: { assert(false); break; }
|
|
}
|
|
}
|
|
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.title(time_title);
|
|
std::string total_filament_str = _u8L("Total Filament");
|
|
std::string model_filament_str = _u8L("Model Filament");
|
|
std::string cost_str = _u8L("Cost");
|
|
std::string prepare_str = _u8L("Prepare time");
|
|
std::string print_str = _u8L("Model printing time");
|
|
std::string total_str = _u8L("Total time");
|
|
std::string end_str = _u8L("End time");
|
|
|
|
float max_len = window_padding + 2 * ImGui::GetStyle().ItemSpacing.x;
|
|
if (time_mode.layers_times.empty())
|
|
max_len += ImGui::CalcTextSize(total_str.c_str()).x;
|
|
else {
|
|
if (m_view_type == EViewType::FeatureType)
|
|
max_len += std::max(ImGui::CalcTextSize(cost_str.c_str()).x,
|
|
std::max(ImGui::CalcTextSize(print_str.c_str()).x,
|
|
std::max(std::max(ImGui::CalcTextSize(prepare_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x),
|
|
std::max(ImGui::CalcTextSize(total_filament_str.c_str()).x, ImGui::CalcTextSize(model_filament_str.c_str()).x))));
|
|
else
|
|
max_len += std::max(ImGui::CalcTextSize(print_str.c_str()).x,
|
|
(std::max(ImGui::CalcTextSize(prepare_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x)));
|
|
}
|
|
|
|
if (m_view_type == EViewType::FeatureType) {
|
|
//BBS display filament cost
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(total_filament_str + ":");
|
|
ImGui::SameLine(max_len);
|
|
//BBS: use current plater's print statistics
|
|
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
|
char buf[64];
|
|
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / koef);
|
|
imgui.text(buf);
|
|
ImGui::SameLine();
|
|
::sprintf(buf, imperial_units ? " %.2f oz" : " %.2f g", ps.total_weight / unit_conver);
|
|
imgui.text(buf);
|
|
//xiamian-
|
|
/*ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(model_filament_str + ":");
|
|
ImGui::SameLine(max_len);
|
|
auto exlude_m = total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m;
|
|
auto exlude_g = total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g;
|
|
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / koef - exlude_m);
|
|
imgui.text(buf);
|
|
ImGui::SameLine();
|
|
::sprintf(buf, imperial_units ? " %.2f oz" : " %.2f g", (ps.total_weight - exlude_g) / unit_conver);
|
|
imgui.text(buf);*/
|
|
|
|
//BBS: display cost of filaments
|
|
//xiamian-
|
|
/*ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(cost_str + ":");
|
|
ImGui::SameLine(max_len);
|
|
::sprintf(buf, "%.2f", ps.total_cost);
|
|
imgui.text(buf);*/
|
|
}
|
|
|
|
auto role_time = [time_mode](ExtrusionRole role) {
|
|
auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair<ExtrusionRole, float>& item) { return role == item.first; });
|
|
return (it != time_mode.roles_times.end()) ? it->second : 0.0f;
|
|
};
|
|
//BBS: start gcode is mostly same with prepeare time
|
|
//xiamian-
|
|
/*if (time_mode.prepare_time != 0.0f) {
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(prepare_str + ":");
|
|
ImGui::SameLine(max_len);
|
|
imgui.text(short_time(get_time_dhms(time_mode.prepare_time)));
|
|
}*/
|
|
//xiamian-
|
|
/*ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(print_str + ":");
|
|
ImGui::SameLine(max_len);
|
|
imgui.text(short_time(get_time_dhms(time_mode.time - time_mode.prepare_time)));*/
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(total_str + ":");
|
|
ImGui::SameLine(max_len);
|
|
imgui.text(short_time(get_time_dhms(time_mode.time)));
|
|
//xiamian+
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
imgui.text(end_str + ":");
|
|
ImGui::SameLine(max_len);
|
|
// 获取当前时间点
|
|
auto now = std::chrono::system_clock::now();
|
|
// 将当前时间点转换为time_t对象
|
|
std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
|
|
// 创建tm结构体用于存储本地时间信息
|
|
std::tm local_tm = *std::localtime(&now_time_t);
|
|
// 计算总秒数(包括小数部分)
|
|
double total_seconds = static_cast<double>(std::mktime(&local_tm)) + time_mode.time;
|
|
// 将总秒数转换回time_t对象
|
|
std::time_t new_time_t = static_cast<std::time_t>(total_seconds);
|
|
// 更新tm结构体以反映新的时间
|
|
std::tm* new_local_tm = std::localtime(&new_time_t);
|
|
// 格式化输出新的时间字符串
|
|
std::ostringstream oss;
|
|
//oss << std::put_time(new_local_tm, "%Y-%m-%d %H:%M:%S");
|
|
oss << std::put_time(new_local_tm, "%Y-%m-%d %H:%M");
|
|
imgui.text(oss.str());
|
|
//shangmian+
|
|
auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) {
|
|
if (can_show_mode_button(mode)) {
|
|
if (imgui.button(label)) {
|
|
m_time_estimate_mode = mode;
|
|
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
imgui.set_requires_extra_frame();
|
|
#else
|
|
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
|
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
|
|
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
}
|
|
}
|
|
};
|
|
|
|
switch (m_time_estimate_mode) {
|
|
case PrintEstimatedStatistics::ETimeMode::Normal: {
|
|
show_mode_button(_L("Switch to silent mode"), PrintEstimatedStatistics::ETimeMode::Stealth);
|
|
break;
|
|
}
|
|
case PrintEstimatedStatistics::ETimeMode::Stealth: {
|
|
show_mode_button(_L("Switch to normal mode"), PrintEstimatedStatistics::ETimeMode::Normal);
|
|
break;
|
|
}
|
|
default : { assert(false); break; }
|
|
}
|
|
}
|
|
|
|
if (m_view_type == EViewType::ColorPrint) {
|
|
ImGui::Spacing();
|
|
ImGui::Dummy({ window_padding, window_padding });
|
|
ImGui::SameLine();
|
|
offsets = calculate_offsets({ { _u8L("Options"), { ""}}, { _u8L("Display"), {""}} }, icon_size);
|
|
append_headers({ {_u8L("Options"), offsets[0] }, { _u8L("Display"), offsets[1]} });
|
|
for (auto item : options_items)
|
|
append_option_item(item, offsets);
|
|
}
|
|
|
|
legend_height = ImGui::GetCurrentWindow()->Size.y;
|
|
ImGui::Dummy({ window_padding, window_padding});
|
|
imgui.end();
|
|
ImGui::PopStyleColor(6);
|
|
ImGui::PopStyleVar(2);
|
|
}
|
|
|
|
void GCodeViewer::push_combo_style()
|
|
{
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0,8.0));
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.0f, 0.0f, 0.0f, 0.8f));
|
|
ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
|
}
|
|
void GCodeViewer::pop_combo_style()
|
|
{
|
|
ImGui::PopStyleVar(3);
|
|
ImGui::PopStyleColor(8);
|
|
}
|
|
|
|
void GCodeViewer::render_slider(int canvas_width, int canvas_height) {
|
|
m_moves_slider->render(canvas_width, canvas_height);
|
|
m_layers_slider->render(canvas_width, canvas_height);
|
|
}
|
|
|
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
|
void GCodeViewer::render_statistics()
|
|
{
|
|
static const float offset = 275.0f;
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
|
|
auto add_time = [this, &imgui](const std::string& label, int64_t time) {
|
|
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
|
|
ImGui::SameLine(offset);
|
|
imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast<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
|
|
|