GCodeViewer -> Estimated printing times shown in the legend

This commit is contained in:
enricoturri1966 2020-08-06 10:15:34 +02:00
parent 8fc5be7e4f
commit 2aa1c2776c
5 changed files with 320 additions and 10 deletions

View File

@ -63,6 +63,6 @@
#define TIME_ESTIMATE_DEFAULT 1 #define TIME_ESTIMATE_DEFAULT 1
#define TIME_ESTIMATE_MODAL 2 #define TIME_ESTIMATE_MODAL 2
#define TIME_ESTIMATE_LEGEND 3 #define TIME_ESTIMATE_LEGEND 3
#define GCODE_VIEWER_TIME_ESTIMATE TIME_ESTIMATE_MODAL #define GCODE_VIEWER_TIME_ESTIMATE TIME_ESTIMATE_LEGEND
#endif // _prusaslicer_technologies_h_ #endif // _prusaslicer_technologies_h_

View File

@ -411,6 +411,9 @@ void GCodeViewer::reset()
#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE #if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE
m_time_statistics.reset(); m_time_statistics.reset();
#endif // GCODE_VIEWER_TIME_ESTIMATE #endif // GCODE_VIEWER_TIME_ESTIMATE
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal;
#endif // GCODE_VIEWER_TIME_ESTIMATE
#if ENABLE_GCODE_VIEWER_STATISTICS #if ENABLE_GCODE_VIEWER_STATISTICS
m_statistics.reset_all(); m_statistics.reset_all();
@ -1375,8 +1378,21 @@ void GCodeViewer::render_legend() const
Line Line
}; };
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast<size_t>(m_time_estimate_mode)];
float icon_size = ImGui::GetTextLineHeight();
float percent_bar_size = 2.0f * ImGui::GetTextLineHeight();
auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label,
bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array<float, 2>& offsets = { 0.0f, 0.0f },
std::function<void()> callback = nullptr) {
if (!visible)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
#else
auto append_item = [this, draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function<void()> callback = nullptr) { auto append_item = [this, draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function<void()> callback = nullptr) {
float icon_size = ImGui::GetTextLineHeight(); float icon_size = ImGui::GetTextLineHeight();
#endif // GCODE_VIEWER_TIME_ESTIMATE
ImVec2 pos = ImGui::GetCursorScreenPos(); ImVec2 pos = ImGui::GetCursorScreenPos();
switch (type) switch (type)
{ {
@ -1438,9 +1454,49 @@ void GCodeViewer::render_legend() const
if (callback != nullptr) { if (callback != nullptr) {
if (ImGui::MenuItem(label.c_str())) if (ImGui::MenuItem(label.c_str()))
callback(); callback();
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
else {
// show tooltip
if (ImGui::IsItemHovered()) {
if (!visible)
ImGui::PopStyleVar();
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROND);
ImGui::BeginTooltip();
imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show"));
ImGui::EndTooltip();
ImGui::PopStyleColor();
if (!visible)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
// to avoid the tooltip to change size when moving the mouse
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
}
}
if (!time.empty()) {
ImGui::SameLine(offsets[0]);
imgui.text(time);
ImGui::SameLine(offsets[1]);
pos = ImGui::GetCursorScreenPos();
float width = percent_bar_size * percent / max_percent;
draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f },
ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT));
ImGui::Dummy({ percent_bar_size, icon_size });
ImGui::SameLine();
char buf[64];
::sprintf(buf, "%.1f%%", 100.0f * percent);
ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
}
#endif // GCODE_VIEWER_TIME_ESTIMATE
} }
else else
imgui.text(label); imgui.text(label);
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
if (!visible)
ImGui::PopStyleVar();
#endif // GCODE_VIEWER_TIME_ESTIMATE
}; };
auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) { auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) {
@ -1461,6 +1517,34 @@ void GCodeViewer::render_legend() const
} }
}; };
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
auto append_headers = [&imgui](const std::array<std::string, 3>& texts, const std::array<float, 2>& offsets) {
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[0]);
ImGui::SameLine(offsets[0]);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[1]);
ImGui::SameLine(offsets[1]);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[2]);
ImGui::Separator();
};
auto max_width = [](const std::vector<std::string>& items, const std::string& title, float extra_size = 0.0f) {
float ret = ImGui::CalcTextSize(title.c_str()).x;
for (const std::string& item : items) {
ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x);
}
return ret;
};
auto calculate_offsets = [max_width](const std::vector<std::string>& labels, const std::vector<std::string>& times,
const std::array<std::string, 2>& titles, float extra_size = 0.0f) {
const ImGuiStyle& style = ImGui::GetStyle();
std::array<float, 2> ret = { 0.0f, 0.0f };
ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x;
ret[1] = ret[0] + max_width(times, titles[1]) + style.ItemSpacing.x;
return ret;
};
#endif // GCODE_VIEWER_TIME_ESTIMATE
auto color_print_ranges = [this](unsigned char extruder_id, const std::vector<CustomGCode::Item>& custom_gcode_per_print_z) { 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; std::vector<std::pair<Color, std::pair<double, double>>> ret;
ret.reserve(custom_gcode_per_print_z.size()); ret.reserve(custom_gcode_per_print_z.size());
@ -1508,10 +1592,83 @@ void GCodeViewer::render_legend() const
return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm");
}; };
// extrusion paths -> title #if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
auto role_time_and_percent = [this, 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);
};
// data used to properly align items in columns when showing time
std::array<float, 2> offsets = { 0.0f, 0.0f };
std::vector<std::string> labels;
std::vector<std::string> times;
std::vector<float> percents;
float max_percent = 0.0f;
if (m_view_type == EViewType::FeatureType) {
// calculate offsets to align time/percentage data
for (size_t i = 0; i < m_roles.size(); ++i) {
ExtrusionRole role = m_roles[i];
if (role < erCount) {
labels.push_back(_u8L(ExtrusionEntity::role_to_string(role)));
auto [time, percent] = role_time_and_percent(role);
times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : "");
percents.push_back(percent);
max_percent = std::max(max_percent, percent);
}
}
offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size);
}
// total estimated printing time section
if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType ||
(m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) {
ImGui::AlignTextToFramePadding();
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Estimated printing time") + ":");
ImGui::SameLine();
imgui.text(short_time(get_time_dhms(time_mode.time)));
auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) {
if (m_time_statistics.modes[static_cast<size_t>(mode)].roles_times.size() > 0) {
ImGui::SameLine(0.0f, 10.0f);
if (imgui.button(label)) {
m_time_estimate_mode = mode;
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
}
}
};
switch (m_time_estimate_mode)
{
case PrintEstimatedTimeStatistics::ETimeMode::Normal:
{
show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth);
break;
}
case PrintEstimatedTimeStatistics::ETimeMode::Stealth:
{
show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal);
break;
}
}
ImGui::Spacing();
}
#endif // GCODE_VIEWER_TIME_ESTIMATE
// extrusion paths section -> title
switch (m_view_type) switch (m_view_type)
{ {
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
case EViewType::FeatureType:
{
append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets);
break;
}
#else
case EViewType::FeatureType: { imgui.title(_u8L("Feature type")); break; } case EViewType::FeatureType: { imgui.title(_u8L("Feature type")); break; }
#endif // GCODE_VIEWER_TIME_ESTIMATE
case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; }
case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; }
case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; }
@ -1522,28 +1679,38 @@ void GCodeViewer::render_legend() const
default: { break; } default: { break; }
} }
// extrusion paths -> items // extrusion paths section -> items
switch (m_view_type) switch (m_view_type)
{ {
case EViewType::FeatureType: case EViewType::FeatureType:
{ {
for (ExtrusionRole role : m_roles) { for (size_t i = 0; i < m_roles.size(); ++i) {
ExtrusionRole role = m_roles[i];
if (role >= erCount)
continue;
bool visible = is_visible(role); bool visible = is_visible(role);
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast<unsigned int>(role)], labels[i],
visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() {
#else
if (!visible) if (!visible)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast<unsigned int>(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast<unsigned int>(role)], _u8L(ExtrusionEntity::role_to_string(role)),
if (role < erCount) { [this, role, visible]() {
m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); #endif // GCODE_VIEWER_TIME_ESTIMATE
m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role);
// update buffers' render paths // update buffers' render paths
refresh_render_paths(false, false); refresh_render_paths(false, false);
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->update_preview_bottom_toolbar(); wxGetApp().plater()->update_preview_bottom_toolbar();
} }
}); );
#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_LEGEND
if (!visible) if (!visible)
ImGui::PopStyleVar(); ImGui::PopStyleVar();
#endif // GCODE_VIEWER_TIME_ESTIMATE
} }
break; break;
} }
@ -1621,7 +1788,139 @@ void GCodeViewer::render_legend() const
default: { break; } default: { break; }
} }
// travel paths #if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
// 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;
};
using PartialTimes = std::vector<PartialTime>;
auto generate_partial_times = [this](const TimesList& times) {
PartialTimes items;
std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
int extruders_count = wxGetApp().extruders_edited_cnt();
std::vector<Color> last_color(extruders_count);
for (int i = 0; i < extruders_count; ++i) {
last_color[i] = m_tool_colors[i];
}
int last_extruder_id = 1;
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, Color(), 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, Color(), Color(), time_rec.second });
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, Color(), Color(), time_rec.second });
break;
}
default: { break; }
}
}
return items;
};
auto append_color = [this, &imgui](const Color& color1, const Color& color2, std::array<float, 2>& 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;
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }), 6);
center.x += icon_size;
draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }), 6);
ImGui::SameLine(offsets[0]);
imgui.text(short_time(get_time_dhms(times.second - times.first)));
};
PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times);
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)));
}
offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size);
ImGui::Spacing();
append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets);
for (const PartialTime& item : partial_times) {
switch (item.type)
{
case PartialTime::EType::Print:
{
imgui.text(_u8L("Print"));
ImGui::SameLine(offsets[0]);
imgui.text(short_time(get_time_dhms(item.times.second)));
ImGui::SameLine(offsets[1]);
imgui.text(short_time(get_time_dhms(item.times.first)));
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(item.color1, item.color2, offsets, item.times);
break;
}
}
}
}
}
#endif // GCODE_VIEWER_TIME_ESTIMATE
// travel paths section
if (m_buffers[buffer_id(EMoveType::Travel)].visible) { if (m_buffers[buffer_id(EMoveType::Travel)].visible) {
switch (m_view_type) switch (m_view_type)
{ {
@ -1671,7 +1970,7 @@ void GCodeViewer::render_legend() const
#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR
}; };
// options // options section
if (any_option_available()) { if (any_option_available()) {
// title // title
ImGui::Spacing(); ImGui::Spacing();

View File

@ -350,6 +350,9 @@ private:
bool m_time_estimate_enabled{ false }; bool m_time_estimate_enabled{ false };
#endif // GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL #endif // GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL
#endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE #endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
mutable PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal };
#endif // GCODE_VIEWER_TIME_ESTIMATE
#if ENABLE_GCODE_VIEWER_STATISTICS #if ENABLE_GCODE_VIEWER_STATISTICS
mutable Statistics m_statistics; mutable Statistics m_statistics;
#endif // ENABLE_GCODE_VIEWER_STATISTICS #endif // ENABLE_GCODE_VIEWER_STATISTICS

View File

@ -1458,7 +1458,11 @@ wxString Preview::get_option_type_string(OptionType type) const
case OptionType::CustomGCodes: { return _L("Custom GCodes"); } case OptionType::CustomGCodes: { return _L("Custom GCodes"); }
case OptionType::Shells: { return _L("Shells"); } case OptionType::Shells: { return _L("Shells"); }
case OptionType::ToolMarker: { return _L("Tool marker"); } case OptionType::ToolMarker: { return _L("Tool marker"); }
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
case OptionType::Legend: { return _L("Legend/Estimated printing time"); }
#else
case OptionType::Legend: { return _L("Legend"); } case OptionType::Legend: { return _L("Legend"); }
#endif // GCODE_VIEWER_TIME_ESTIMATE
#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE #if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE
case OptionType::TimeEstimate: { return _L("Estimated printing time"); } case OptionType::TimeEstimate: { return _L("Estimated printing time"); }
#endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE #endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE

View File

@ -203,7 +203,11 @@ void KBShortcutsDialog::fill_shortcuts()
{ L("Arrow Down"), L("Lower Layer") }, { L("Arrow Down"), L("Lower Layer") },
{ "U", L("Upper Layer") }, { "U", L("Upper Layer") },
{ "D", L("Lower Layer") }, { "D", L("Lower Layer") },
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND
{ "L", L("Show/Hide Legend/Estimated printing time") },
#else
{ "L", L("Show/Hide Legend") }, { "L", L("Show/Hide Legend") },
#endif // GCODE_VIEWER_TIME_ESTIMATE
#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT #if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT
{ "T", L("Show/Hide Estimated printing time") } { "T", L("Show/Hide Estimated printing time") }
#elif GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL #elif GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL