PrusaSlicer/src/slic3r/GUI/DoubleSliderForLayers.cpp
2024-04-24 10:54:12 +02:00

1047 lines
41 KiB
C++

///|/ Copyright (c) Prusa Research 2020 - 2023 Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "DoubleSliderForLayers.hpp"
#include "libslic3r/Utils.hpp" // -> get_time_dhms()
#include "libslic3r/format.hpp" // -> format()
#include "I18N.hpp"
#include <cmath>
#include "ImGuiWrapper.hpp"
#include "imgui/imgui_internal.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <imgui/imgui_internal.h>
using namespace Slic3r;
using namespace CustomGCode;
using Slic3r::format;
namespace DoubleSlider {
static const float VERTICAL_SLIDER_WIDTH = 105.0f;
DSForLayers::DSForLayers( int lowerValue,
int higherValue,
int minValue,
int maxValue,
bool allow_editing) :
m_allow_editing(allow_editing)
{
#ifdef __WXOSX__
is_osx = true;
#endif //__WXOSX__
Init(lowerValue, higherValue, minValue, maxValue, "layers_slider", false);
m_ctrl.ShowLabelOnMouseMove(true);
m_ctrl.set_get_label_on_move_cb([this](int pos) { return m_show_estimated_times ? get_label(pos, ltEstimatedTime) : ""; });
m_ctrl.set_extra_draw_cb([this](const ImRect& draw_rc) {return draw_ticks(draw_rc); });
m_ticks.set_values(&m_values);
}
Info DSForLayers::GetTicksValues() const
{
Info custom_gcode_per_print_z;
std::vector<CustomGCode::Item>& values = custom_gcode_per_print_z.gcodes;
const int val_size = m_values.size();
if (!m_values.empty())
for (const TickCode& tick : m_ticks.ticks) {
if (tick.tick > val_size)
break;
values.emplace_back(CustomGCode::Item{ m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra });
}
custom_gcode_per_print_z.mode = m_mode;
return custom_gcode_per_print_z;
}
void DSForLayers::SetTicksValues(const Info& custom_gcode_per_print_z)
{
if (m_values.empty()) {
m_ticks.mode = m_mode;
return;
}
if (m_cb_get_print)
m_ticks.set_print(m_cb_get_print());
const bool was_empty = m_ticks.empty();
m_ticks.set_ticks(custom_gcode_per_print_z);
if (!was_empty && m_ticks.empty())
// Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
process_ticks_changed();
update_draw_scroll_line_cb();
}
void DSForLayers::SetLayersTimes(const std::vector<float>& layers_times, float total_time)
{
m_layers_times.clear();
if (layers_times.empty())
return;
m_layers_times.resize(layers_times.size(), 0.0);
m_layers_times[0] = layers_times[0];
for (size_t i = 1; i < layers_times.size(); i++)
m_layers_times[i] = m_layers_times[i - 1] + layers_times[i];
// Erase duplicates values from m_values and save it to the m_layers_values
// They will be used for show the correct estimated time for MM print, when "No sparce layer" is enabled
// See https://github.com/prusa3d/PrusaSlicer/issues/6232
if (m_ticks.is_wipe_tower && m_values.size() != m_layers_times.size()) {
m_layers_values = m_values;
sort(m_layers_values.begin(), m_layers_values.end());
m_layers_values.erase(unique(m_layers_values.begin(), m_layers_values.end()), m_layers_values.end());
// When whipe tower is used to the end of print, there is one layer which is not marked in layers_times
// So, add this value from the total print time value
if (m_layers_values.size() != m_layers_times.size())
for (size_t i = m_layers_times.size(); i < m_layers_values.size(); i++)
m_layers_times.push_back(total_time);
}
}
void DSForLayers::SetLayersTimes(const std::vector<double>& layers_times)
{
m_ticks.is_wipe_tower = false;
m_layers_times = layers_times;
for (size_t i = 1; i < m_layers_times.size(); i++)
m_layers_times[i] += m_layers_times[i - 1];
}
void DSForLayers::SetDrawMode(bool is_sla_print, bool is_sequential_print)
{
m_draw_mode = is_sla_print ? dmSlaPrint :
is_sequential_print ? dmSequentialFffPrint :
dmRegular;
update_draw_scroll_line_cb();
}
void DSForLayers::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder)
{
m_mode = !is_one_extruder_printed_model ? MultiExtruder :
only_extruder < 0 ? SingleExtruder :
MultiAsSingle;
if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode))
m_ticks.mode = m_mode;
m_ticks.only_extruder_id = only_extruder;
m_ticks.is_wipe_tower = m_mode != SingleExtruder;
if (m_mode != SingleExtruder)
UseDefaultColors(false);
}
void DSForLayers::SetExtruderColors( const std::vector<std::string>& extruder_colors)
{
m_ticks.colors = extruder_colors;
}
bool DSForLayers::is_new_print(const std::string& idxs)
{
if (idxs == "sla" || idxs == m_print_obj_idxs)
return false;
m_print_obj_idxs = idxs;
return true;
}
void DSForLayers::update_draw_scroll_line_cb()
{
if (m_ticks.empty() || m_draw_mode == dmSequentialFffPrint || m_draw_mode == dmSlaPrint)
m_ctrl.set_draw_scroll_line_cb(nullptr);
else
m_ctrl.set_draw_scroll_line_cb([this](const ImRect& scroll_line, const ImRect& slideable_region) { draw_colored_band(scroll_line, slideable_region); });
}
using namespace ImGui;
void DSForLayers::draw_ticks(const ImRect& slideable_region)
{
//if(m_draw_mode != dmRegular)
// return;
//if (m_ticks.empty() || m_mode == MultiExtruder)
// return;
if (m_ticks.empty())
return;
const ImVec2 tick_border = ImVec2(23.0f, 2.0f) * m_scale;
// distance form center begin end
const ImVec2 tick_size = ImVec2(19.0f, 11.0f) * m_scale;
const float tick_width = 1.0f * m_scale;
const float icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height;
const float icon_offset = 0.5f * icon_side;;
const ImU32 tick_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_ORANGE_DARK);
const ImU32 tick_hovered_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_WINDOW_BACKGROUND);
auto get_tick_pos = [this, slideable_region](int tick) {
return m_ctrl.GetPositionInRect(tick, slideable_region);
};
std::set<TickCode>::const_iterator tick_it = m_ticks.ticks.begin();
while (tick_it != m_ticks.ticks.end())
{
float tick_pos = get_tick_pos(tick_it->tick);
//draw tick hover box when hovered
ImRect tick_hover_box = ImRect(slideable_region.GetCenter().x - tick_border.x, tick_pos - tick_border.y,
slideable_region.GetCenter().x + tick_border.x, tick_pos + tick_border.y - tick_width);
if (ImGui::IsMouseHoveringRect(tick_hover_box.Min, tick_hover_box.Max)) {
ImGui::RenderFrame(tick_hover_box.Min, tick_hover_box.Max, tick_hovered_clr, false);
break;
}
++tick_it;
}
auto active_tick_it = m_ticks.ticks.find(TickCode{ m_ctrl.GetActivePos() });
tick_it = m_ticks.ticks.begin();
while (tick_it != m_ticks.ticks.end())
{
float tick_pos = get_tick_pos(tick_it->tick);
//draw ticks
ImRect tick_left = ImRect(slideable_region.GetCenter().x - tick_size.x, tick_pos - tick_width, slideable_region.GetCenter().x - tick_size.y, tick_pos);
ImRect tick_right = ImRect(slideable_region.GetCenter().x + tick_size.y, tick_pos - tick_width, slideable_region.GetCenter().x + tick_size.x, tick_pos);
ImGui::RenderFrame(tick_left.Min, tick_left.Max, tick_clr, false);
ImGui::RenderFrame(tick_right.Min, tick_right.Max, tick_clr, false);
ImVec2 icon_pos = ImVec2(tick_right.Max.x + 0.5f * icon_offset, tick_pos - icon_offset);
std::string btn_label = "tick " + std::to_string(tick_it->tick);
//draw tick icon-buttons
bool activate_this_tick = false;
if (tick_it == active_tick_it && m_allow_editing) {
// delete tick
if (render_button(ImGui::RemoveTick, ImGui::RemoveTickHovered, btn_label, icon_pos, m_ctrl.IsActiveHigherThumb() ? fiHigherThumb : fiLowerThumb, tick_it->tick)) {
m_ticks.ticks.erase(tick_it);
process_ticks_changed();
break;
}
}
else if (m_draw_mode != dmRegular)// if we have non-regular draw mode, all ticks should be marked with error icon
activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick);
else if (tick_it->type == ColorChange || tick_it->type == ToolChange) {
if (m_ticks.is_conflict_tick(*tick_it, m_mode, m_values[tick_it->tick]))
activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick);
}
else if (tick_it->type == CustomGCode::PausePrint)
activate_this_tick = render_button(ImGui::PausePrint, ImGui::PausePrintHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick);
else
activate_this_tick = render_button(ImGui::EditGCode, ImGui::EditGCodeHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick);
if (activate_this_tick) {
m_ctrl.IsActiveHigherThumb() ? SetHigherPos(tick_it->tick) : SetLowerPos(tick_it->tick);
break;
}
++tick_it;
}
}
static std::array<float, 4> decode_color_to_float_array(const std::string color)
{
auto hex_digit_to_int = [](const char c) {
return
(c >= '0' && c <= '9') ? int(c - '0') :
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
};
// set alpha to 1.0f by default
std::array<float, 4> ret = { 0, 0, 0, 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) / 255.0f;
}
}
return ret;
}
std::string encode_color_from_float_array(const std::array<float, 4>& color)
{
char buffer[64];
::sprintf(buffer, "#%02X%02X%02X", int(color[0] * 255.0f), int(color[1] * 255.0f), int(color[2] * 255.0f));
return std::string(buffer);
}
void DSForLayers::draw_colored_band(const ImRect& groove, const ImRect& slideable_region)
{
if (m_ticks.empty() || m_draw_mode == dmSequentialFffPrint)
return;
ImVec2 blank_padding = ImVec2(0.5f * m_ctrl.GetGrooveRect().GetWidth(), 2.0f * m_scale);
float blank_width = 1.0f * m_scale;
ImRect blank_rect = ImRect(groove.GetCenter().x - blank_width, groove.Min.y, groove.GetCenter().x + blank_width, groove.Max.y);
ImRect main_band = ImRect(blank_rect);
main_band.Expand(blank_padding);
auto draw_band = [](const ImU32& clr, const ImRect& band_rc) {
ImGui::RenderFrame(band_rc.Min, band_rc.Max, clr, false, band_rc.GetWidth() * 0.5);
//cover round corner
ImGui::RenderFrame(ImVec2(band_rc.Min.x, band_rc.Max.y - band_rc.GetWidth() * 0.5), band_rc.Max, clr, false);
};
auto draw_main_band = [&main_band](const ImU32& clr) {
ImGui::RenderFrame(main_band.Min, main_band.Max, clr, false, main_band.GetWidth() * 0.5);
};
//draw main colored band
const int default_color_idx = m_mode == MultiAsSingle ? std::max<int>(m_ticks.only_extruder_id - 1, 0) : 0;
std::array<float, 4>rgba = decode_color_to_float_array(m_ticks.colors[default_color_idx]);
ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f);
draw_main_band(band_clr);
static float tick_pos;
std::set<TickCode>::const_iterator tick_it = m_ticks.ticks.begin();
while (tick_it != m_ticks.ticks.end())
{
//get position from tick
tick_pos = m_ctrl.GetPositionInRect(tick_it->tick, slideable_region);
ImRect band_rect = ImRect(ImVec2(main_band.Min.x, std::min(tick_pos, main_band.Min.y)),
ImVec2(main_band.Max.x, std::min(tick_pos, main_band.Max.y)));
if (main_band.Contains(band_rect)) {
if ((m_mode == SingleExtruder && tick_it->type == ColorChange) ||
(m_mode == MultiAsSingle && (tick_it->type == ToolChange || tick_it->type == ColorChange)))
{
const std::string clr_str = m_mode == SingleExtruder ? tick_it->color :
tick_it->type == ToolChange ?
m_ticks.get_color_for_tool_change_tick(tick_it) :
m_ticks.get_color_for_color_change_tick(tick_it);
if (!clr_str.empty()) {
std::array<float, 4>rgba = decode_color_to_float_array(clr_str);
ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f);
if (tick_it->tick == 0)
draw_main_band(band_clr);
else
draw_band(band_clr, band_rect);
}
}
}
tick_it++;
}
}
void DSForLayers::render_menu()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 10.0f) * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 4.0f * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, ImGui::GetStyle().ItemSpacing.y });
ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_ChildRounding, 4.0f * m_scale);
ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
if (m_ctrl.IsRClickOnThumb())
ImGui::OpenPopup("slider_full_menu_popup");
else if (m_show_just_color_change_menu)
ImGui::OpenPopup("slider_add_tick_menu_popup");
else if (m_show_cog_menu)
ImGui::OpenPopup("cog_menu_popup");
if (m_allow_editing)
render_add_tick_menu();
render_cog_menu();
ImGui::PopStyleColor(1);
ImGui::PopStyleVar(4);
ImGuiContext& context = *GImGui;
if (context.IO.MouseReleased[0]) {
m_show_just_color_change_menu = false;
m_show_cog_menu = false;
}
}
void DSForLayers::render_add_tick_menu()
{
if (ImGui::BeginPopup("slider_full_menu_popup")) {
if (m_mode == SingleExtruder) {
if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Color Change").c_str(), "")) {
add_code_as_tick(ColorChange);
}
}
else
render_multi_extruders_menu();
if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Pause").c_str(), "")) {
add_code_as_tick(CustomGCode::PausePrint);
}
if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Custom G-code").c_str(), "")) {
add_code_as_tick(Custom);
}
if (!gcode(Template).empty() &&
ImGuiPureWrap::menu_item_with_icon(_u8L("Add Custom Template").c_str(), "")) {
add_code_as_tick(Template);
}
ImGui::EndPopup();
return;
}
const std::string longest_menu_name = format(_u8L("Add color change (%1%) for:"), gcode(ColorChange));
const ImVec2 label_size = ImGui::CalcTextSize(longest_menu_name.c_str(), NULL, true);
const ImRect active_thumb_rect = m_ctrl.GetActiveThumbRect();
const ImVec2 pos = active_thumb_rect.GetCenter();
ImGui::SetNextWindowPos(ImVec2(pos.x - label_size.x - active_thumb_rect.GetWidth(), pos.y));
if (ImGui::BeginPopup("slider_add_tick_menu_popup")) {
render_multi_extruders_menu();
ImGui::EndPopup();
}
}
void DSForLayers::render_multi_extruders_menu()
{
std::vector<std::string> colors;
if (m_cb_get_extruder_colors)
colors = m_cb_get_extruder_colors();
int extruders_cnt = colors.size();
if (extruders_cnt > 1) {
const int tick = m_ctrl.GetActivePos();
if (m_mode == MultiAsSingle) {
const std::string menu_name = _u8L("Change extruder");
if (ImGuiPureWrap::begin_menu(menu_name.c_str())) {
std::array<int, 2> active_extruders = m_ticks.get_active_extruders_for_tick(tick, m_mode);
for (int i = 1; i <= extruders_cnt; i++) {
const bool is_active_extruder = i == active_extruders[0] || i == active_extruders[1];
std::string item_name = format(_u8L("Extruder %d"), i);
if (is_active_extruder)
item_name += " (" + _u8L("active") + ")";
std::array<float, 4> rgba = decode_color_to_float_array(colors[i - 1]);
ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f);
if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), "", ImVec2(14, 14) * m_scale, icon_clr, false, !is_active_extruder))
add_code_as_tick(ToolChange, i);
}
ImGuiPureWrap::end_menu();
}
}
const std::string menu_name = format(_u8L("Add color change (%1%) for:"), gcode(ColorChange));
if (ImGuiPureWrap::begin_menu(menu_name.c_str())) {
std::set<int> used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_values[tick]);
for (int i = 1; i <= extruders_cnt; i++) {
const bool is_used_extruder = used_extruders_for_tick.empty() ? true : // #ys_FIXME till used_extruders_for_tick doesn't filled correct for mmMultiExtruder
used_extruders_for_tick.find(i) != used_extruders_for_tick.end();
std::string item_name = format(_u8L("Extruder %d"), i);
if (is_used_extruder)
item_name += " (" + _u8L("used") + ")";
if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), ""))
add_code_as_tick(ColorChange, i);
}
ImGuiPureWrap::end_menu();
}
}
}
void DSForLayers::render_color_picker()
{
ImGuiContext& context = *GImGui;
const std::string title = _u8L("Select color for color change");
if (m_show_color_picker) {
ImGuiPureWrap::set_next_window_pos(1200, 200, ImGuiCond_Always, 0.5f, 0.0f);
ImGuiPureWrap::begin(title, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoScrollWithMouse);
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoDragDrop;
auto col = decode_color_to_float_array(m_selectable_color);
if (ImGui::ColorPicker4("color_picker", (float*)&col, misc_flags)) {
m_selectable_color = encode_color_from_float_array(col);
m_show_color_picker = false;
}
ImGuiPureWrap::end();
}
if (auto clr_pcr_win = ImGui::FindWindowByName(title.c_str()); clr_pcr_win && context.CurrentWindow != clr_pcr_win)
m_show_color_picker = false;
}
void DSForLayers::render_cog_menu()
{
const ImVec2 icon_sz = ImVec2(14, 14);
if (ImGui::BeginPopup("cog_menu_popup")) {
if (ImGuiPureWrap::menu_item_with_icon(_u8L("Jump to height").c_str(), "Shift+G")) {
jump_to_value();
}
if (ImGuiPureWrap::menu_item_with_icon(_u8L("Show estimated print time on mouse moving").c_str(), "", icon_sz, 0, m_show_estimated_times)) {
m_show_estimated_times = !m_show_estimated_times;
}
if (m_mode == MultiAsSingle && m_draw_mode == dmRegular &&
ImGuiPureWrap::menu_item_with_icon(_u8L("Set extruder sequence for the entire print").c_str(), "")) {
if (m_ticks.edit_extruder_sequence(m_ctrl.GetMaxPos(), m_mode))
process_ticks_changed();
}
if (m_allow_editing) {
if (ImGuiPureWrap::menu_item_with_icon(_u8L("Use default colors").c_str(), "", icon_sz, 0, m_ticks.used_default_colors())) {
UseDefaultColors(!m_ticks.used_default_colors());
}
if (m_mode != MultiExtruder && m_draw_mode == dmRegular &&
ImGuiPureWrap::menu_item_with_icon(_u8L("Set auto color changes").c_str(), "")) {
auto_color_change();
}
}
ImGui::EndPopup();
}
}
bool DSForLayers::render_button(const wchar_t btn_icon, const wchar_t btn_icon_hovered, const std::string& label_id, const ImVec2& pos, FocusedItem focus, int tick /*= -1*/)
{
ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowBorderSize, 0);
ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text));
int windows_flag = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoScrollWithMouse;
ImGuiPureWrap::set_next_window_pos(pos.x, pos.y, ImGuiCond_Always);
std::string win_name = label_id + "##btn_win";
ImGuiPureWrap::begin(win_name, windows_flag);
ImGuiContext& g = *GImGui;
m_focus = focus;
std::string tooltip = m_allow_editing ? get_tooltip(tick) : "";
ImGui::SetCursorPos(ImVec2(0, 0));
const bool ret = m_imgui->image_button(g.HoveredWindow == g.CurrentWindow ? btn_icon_hovered : btn_icon, tooltip, false);
ImGuiPureWrap::end();
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(3);
return ret;
}
bool DSForLayers::render_jump_to_window(const ImVec2& pos, double* active_value, double min_z, double max_z)
{
if (!m_show_get_jump_value)
return false;
const std::string msg_text = _u8L("Enter the height you want to jump to") + ":";
const std::string win_name = _u8L("Jump to height") + "##btn_win";
const ImVec2 msg_size = ImGui::CalcTextSize(msg_text.c_str(), NULL, true);
const float ctrl_pos_x = msg_size.x + 15 * m_scale;
const float ctrl_width = 50.f * m_scale;
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 12.0f, 8.0f });
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.13f, 0.13f, 0.13f, 0.8f));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text));
ImGuiWindowFlags windows_flag = ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoScrollWithMouse;
ImGui::Begin(win_name.c_str(), &m_show_get_jump_value, windows_flag);
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", msg_text.c_str());
ImGui::SameLine(ctrl_pos_x);
ImGui::PushItemWidth(ctrl_width);
ImGui::InputDouble("##jump_to", active_value, 0.0, 0.0, "%.2f", ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll);
//check if Enter was pressed
bool enter_pressed = ImGui::IsItemDeactivatedAfterEdit();
//check out of range
bool disable_ok = *active_value < min_z || *active_value > max_z;
ImGui::Text("%s", "");
ImGui::SameLine(ctrl_pos_x);
if (disable_ok) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
}
bool ok_pressed = ImGui::Button("OK##jump_to", ImVec2(ctrl_width, 0.f));
if (disable_ok) {
ImGui::PopItemFlag();
ImGui::PopStyleVar();
}
ImGui::End();
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(3);
return enter_pressed || ok_pressed;
}
void DSForLayers::Render(const int canvas_width, const int canvas_height, float extra_scale/* = 0.1f*/, float offset /*= 0.f*/)
{
if (!m_ctrl.IsShown())
return;
m_scale = extra_scale * 0.1f * m_em;
const float action_btn_sz = m_imgui->GetTextureCustomRect(ImGui::DSRevert)->Height;
const float tick_icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height;
ImVec2 pos;
pos.x = canvas_width - VERTICAL_SLIDER_WIDTH * m_scale - tick_icon_side;
pos.y = 1.5f * action_btn_sz + offset;
if (m_allow_editing)
pos.y += 2.f;
ImVec2 size = ImVec2(VERTICAL_SLIDER_WIDTH * m_scale, canvas_height - 4.f * action_btn_sz - offset);
m_ctrl.Init(pos, size, m_scale);
if (m_ctrl.render()) {
// request one more frame if value was changes with mouse wheel
if (GImGui->IO.MouseWheel != 0.0f)
m_imgui->set_requires_extra_frame();
process_thumb_move();
// discard all getters dialogs
m_show_get_jump_value = false;
}
// draw action buttons
const float groove_center_x = m_ctrl.GetGrooveRect().GetCenter().x;
ImVec2 btn_pos = ImVec2(groove_center_x - 0.5f * action_btn_sz, pos.y - 0.75f * action_btn_sz);
if (!m_ticks.empty() && m_allow_editing &&
render_button(ImGui::DSRevert, ImGui::DSRevertHovered, "revert", btn_pos, fiRevertIcon))
discard_all_thicks();
btn_pos.y += 0.5f * action_btn_sz + size.y;
const bool is_one_layer = m_ctrl.IsCombineThumbs();
if (render_button(is_one_layer ? ImGui::Lock : ImGui::Unlock, is_one_layer ? ImGui::LockHovered : ImGui::UnlockHovered, "one_layer", btn_pos, fiOneLayerIcon))
ChangeOneLayerLock();
btn_pos.y += 1.2f * action_btn_sz;
if (render_button(ImGui::DSSettings, ImGui::DSSettingsHovered, "settings", btn_pos, fiCogIcon)) {
m_show_cog_menu = true;
}
if (m_draw_mode == dmSequentialFffPrint && m_ctrl.IsRClickOnThumb()) {
std::string tooltip = _u8L("The sequential print is on.\n"
"It's impossible to apply any custom G-code for objects printing sequentually.");
ImGuiPureWrap::tooltip(tooltip, ImGui::GetFontSize() * 20.0f);
}
else
render_menu();
if (render_jump_to_window(ImVec2(0.5f * canvas_width, 0.5f*canvas_height), &m_jump_to_value,
m_values[m_ctrl.GetMinPos()], m_values[m_ctrl.GetMaxPos()]))
process_jump_to_value();
if (m_allow_editing)
render_color_picker();
}
bool DSForLayers::is_wipe_tower_layer(int tick) const
{
if (!m_ticks.is_wipe_tower || tick >= (int)m_values.size())
return false;
if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1]))
return false;
if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) ||
(tick > 0 && m_values[tick] < m_values[tick - 1]) ) // if there is just one wiping on the layer
return true;
return false;
}
static std::string short_and_splitted_time(const std::string& time)
{
// Parse the dhms time format.
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
if (time.find('d') != std::string::npos)
::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds);
else if (time.find('h') != std::string::npos)
::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds);
else if (time.find('m') != std::string::npos)
::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds);
else if (time.find('s') != std::string::npos)
::sscanf(time.c_str(), "%ds", &seconds);
// Format the dhm time.
auto get_d = [days]() { return format(_u8L("%1%d"), days); };
auto get_h = [hours]() { return format(_u8L("%1%h"), hours); };
auto get_m = [minutes](){ return format(_u8L("%1%m"), minutes); };
auto get_s = [seconds](){ return format(_u8L("%1%s"), seconds); };
if (days > 0)
return format("%1%%2%\n%3%", get_d(), get_h(), get_m());
if (hours > 0) {
if (hours < 10 && minutes < 10 && seconds < 10)
return format("%1%%2%%3%", get_h(), get_m(), get_s());
if (hours > 10 && minutes > 10 && seconds > 10)
return format("%1%\n%2%\n%3%", get_h(), get_m(), get_s());
if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10))
return format("%1%\n%2%%3%", get_h(), get_m(), get_s());
return format("%1%%2%\n%3%", get_h(), get_m(), get_s());
}
if (minutes > 0) {
if (minutes > 10 && seconds > 10)
return format("%1%\n%2%", get_m(), get_s());
return format("%1%%2%", get_m(), get_s());
}
return get_s();
}
std::string DSForLayers::get_label(int pos, LabelType label_type) const
{
const size_t value = pos;
if (m_values.empty())
return format("%1%", pos);
if (value >= m_values.size())
return "ErrVal";
// When "Print Settings -> Multiple Extruders -> No sparse layer" is enabled, then "Smart" Wipe Tower is used for wiping.
// As a result, each layer with tool changes is splited for min 3 parts: first tool, wiping, second tool ...
// So, vertical slider have to respect to this case.
// see https://github.com/prusa3d/PrusaSlicer/issues/6232.
// m_values contains data for all layer's parts,
// but m_layers_values contains just unique Z values.
// Use this function for correct conversion slider position to number of printed layer
auto get_layer_number = [this](int value, LabelType label_type) {
if (label_type == ltEstimatedTime && m_layers_times.empty())
return size_t(-1);
double layer_print_z = m_values[is_wipe_tower_layer(value) ? std::max<int>(value - 1, 0) : value];
auto it = std::lower_bound(m_layers_values.begin(), m_layers_values.end(), layer_print_z - epsilon());
if (it == m_layers_values.end()) {
it = std::lower_bound(m_values.begin(), m_values.end(), layer_print_z - epsilon());
if (it == m_values.end())
return size_t(-1);
return size_t(value);
}
return size_t(it - m_layers_values.begin());
};
if (label_type == ltEstimatedTime) {
if (m_ticks.is_wipe_tower) {
size_t layer_number = get_layer_number(value, label_type);
return (layer_number == size_t(-1) || layer_number == m_layers_times.size()) ? "" : short_and_splitted_time(get_time_dhms(m_layers_times[layer_number]));
}
return value < m_layers_times.size() ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : "";
}
std::string str = format("%1$.2f", m_values[value]);
if (label_type == ltHeight)
return str;
if (label_type == ltHeightWithLayer) {
size_t layer_number = m_ticks.is_wipe_tower ? get_layer_number(value, label_type) + 1 : (m_values.empty() ? value : value + 1);
return format("%1%\n(%2%)", str, layer_number);
}
return "";
}
void DSForLayers::ChangeOneLayerLock()
{
m_ctrl.CombineThumbs(!m_ctrl.IsCombineThumbs());
process_thumb_move();
}
std::string DSForLayers::get_tooltip(int tick/*=-1*/)
{
if (m_focus == fiNone)
return "";
if (m_focus == fiOneLayerIcon)
return _u8L("One layer mode");
if (m_focus == fiRevertIcon)
return _u8L("Discard all custom changes");
if (m_focus == fiCogIcon)
{
return m_mode == MultiAsSingle ?
(boost::format(_u8L("Jump to height %s\n"
"Set ruler mode\n"
"or Set extruder sequence for the entire print")) % "(Shift + G)").str() :
(boost::format(_u8L("Jump to height %s\n"
"or Set ruler mode")) % "(Shift + G)").str();
}
if (m_focus == fiColorBand)
return m_mode != SingleExtruder ? "" :
_u8L("Edit current color - Right click the colored slider segment");
if (m_focus == fiSmartWipeTower)
return _u8L("This is wipe tower layer");
if (m_draw_mode == dmSlaPrint)
return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode
std::string tooltip;
const auto tick_code_it = m_ticks.ticks.find(TickCode{tick});
if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist
{
if (m_draw_mode == dmSequentialFffPrint)
return (_u8L("The sequential print is on.\n"
"It's impossible to apply any custom G-code for objects printing sequentually.") + "\n");
// Show mode as a first string of tooltop
tooltip = " " + _u8L("Print mode") + ": ";
tooltip += (m_mode == SingleExtruder ? SingleExtruderMode :
m_mode == MultiAsSingle ? MultiAsSingleMode :
MultiExtruderMode );
tooltip += "\n\n";
/* Note: just on OSX!!!
* Right click event causes a little scrolling.
* So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick
* Show this information in tooltip
* */
// Show list of actions with new tick
tooltip += ( m_mode == MultiAsSingle ?
_u8L("Add extruder change - Left click") :
m_mode == SingleExtruder ?
_u8L("Add color change - Left click for predefined color or "
"Shift + Left click for custom color selection") :
_u8L("Add color change - Left click") ) + " " +
_u8L("or press \"+\" key") + "\n" + (
is_osx ?
_u8L("Add another code - Ctrl + Left click") :
_u8L("Add another code - Right click") );
}
if (tick_code_it != m_ticks.ticks.end()) // tick exists
{
if (m_draw_mode == dmSequentialFffPrint)
return _u8L("The sequential print is on.\n"
"It's impossible to apply any custom G-code for objects printing sequentually.\n"
"This code won't be processed during G-code generation.");
// Show custom Gcode as a first string of tooltop
std::string space = " ";
tooltip = space;
auto format_gcode = [space](std::string gcode) -> std::string {
// when the tooltip is too long, it starts to flicker, see: https://github.com/prusa3d/PrusaSlicer/issues/7368
// so we limit the number of lines shown
std::vector<std::string> lines;
boost::split(lines, gcode, boost::is_any_of("\n"), boost::token_compress_off);
static const size_t MAX_LINES = 10;
if (lines.size() > MAX_LINES) {
gcode = lines.front() + '\n';
for (size_t i = 1; i < MAX_LINES; ++i) {
gcode += lines[i] + '\n';
}
gcode += "[" + _u8L("continue") + "]\n";
}
boost::replace_all(gcode, "\n", "\n" + space);
return gcode;
};
tooltip +=
tick_code_it->type == ColorChange ?
(m_mode == SingleExtruder ?
format(_u8L("Color change (\"%1%\")"), gcode(ColorChange)) :
format(_u8L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) :
tick_code_it->type == CustomGCode::PausePrint ?
format(_u8L("Pause print (\"%1%\")"), gcode(CustomGCode::PausePrint)) :
tick_code_it->type == Template ?
format(_u8L("Custom template (\"%1%\")"), gcode(Template)) :
tick_code_it->type == ToolChange ?
format(_u8L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) :
format_gcode(tick_code_it->extra);// tick_code_it->type == Custom
// If tick is marked as a conflict (exclamation icon),
// we should to explain why
ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_values[tick]);
if (conflict != ctNone)
tooltip += "\n\n" + _u8L("Note") + "! ";
if (conflict == ctModeConflict)
tooltip += _u8L("G-code associated to this tick mark is in a conflict with print mode.\n"
"Editing it will cause changes of Slider data.");
else if (conflict == ctMeaninglessColorChange)
tooltip += _u8L("There is a color change for extruder that won't be used till the end of print job.\n"
"This code won't be processed during G-code generation.");
else if (conflict == ctMeaninglessToolChange)
tooltip += _u8L("There is an extruder change set to the same extruder.\n"
"This code won't be processed during G-code generation.");
else if (conflict == ctRedundant)
tooltip += _u8L("There is a color change for extruder that has not been used before.\n"
"Check your settings to avoid redundant color changes.");
// Show list of actions with existing tick
if (m_focus == fiActionIcon)
tooltip += "\n\n" + _u8L("Delete tick mark - Left click or press \"-\" key") + "\n" + (
is_osx ?
_u8L("Edit tick mark - Ctrl + Left click") :
_u8L("Edit tick mark - Right click") );
}
return tooltip;
}
void DSForLayers::UseDefaultColors(bool def_colors_on)
{
m_ticks.set_default_colors(def_colors_on);
}
// !ysFIXME draw with imgui
void DSForLayers::auto_color_change()
{
if (m_ticks.auto_color_change(m_mode)) {
update_draw_scroll_line_cb();
process_ticks_changed();
}
}
void DSForLayers::add_code_as_tick(Type type, int selected_extruder/* = -1*/)
{
const int tick = m_ctrl.GetActivePos();
if (!m_ticks.check_ticks_changed_event(type, m_mode)) {
process_ticks_changed();
return;
}
const int extruder = selected_extruder > 0 ? selected_extruder : std::max<int>(1, m_ticks.only_extruder_id);
const auto it = m_ticks.ticks.find(TickCode{ tick });
bool was_ticks = m_ticks.empty();
if ( it == m_ticks.ticks.end() ) {
// try to add tick
if (!m_ticks.add_tick(tick, type, extruder, m_values[tick]))
return;
}
else if (type == ToolChange || type == ColorChange) {
// try to switch tick code to ToolChange or ColorChange accordingly
if (!m_ticks.switch_code_for_tick(it, type, extruder))
return;
}
else
return;
if (was_ticks != m_ticks.empty())
update_draw_scroll_line_cb();
m_show_just_color_change_menu = false;
process_ticks_changed();
}
void DSForLayers::add_current_tick()
{
if (!m_allow_editing)
return;
const int tick = m_ctrl.GetActivePos();
auto it = m_ticks.ticks.find(TickCode{ tick });
if (it != m_ticks.ticks.end()) // this tick is already exist
return;
if (!m_ticks.check_ticks_changed_event(m_mode == MultiAsSingle ? ToolChange : ColorChange, m_mode)) {
process_ticks_changed();
return;
}
if (m_mode == SingleExtruder)
add_code_as_tick(ColorChange);
else {
m_show_just_color_change_menu = true;
m_imgui->set_requires_extra_frame();
}
}
void DSForLayers::delete_current_tick()
{
auto it = m_ticks.ticks.find(TickCode{ m_ctrl.GetActivePos()});
if (it == m_ticks.ticks.end()) // this tick doesn't exist
return;
m_ticks.ticks.erase(it);
process_ticks_changed();
}
void DSForLayers::edit_tick(int tick/* = -1*/)
{
if (tick < 0)
tick = m_ctrl.GetActivePos();
const std::set<TickCode>::iterator it = m_ticks.ticks.find(TickCode{ tick });
if (it == m_ticks.ticks.end()) // this tick doesn't exist
return;
if (!m_ticks.check_ticks_changed_event(it->type, m_mode) ||
m_ticks.edit_tick(it, m_values[it->tick]))
process_ticks_changed();
}
// discard all custom changes on DoubleSlider
void DSForLayers::discard_all_thicks()
{
m_ticks.ticks.clear();
m_ctrl.ResetPositions();
update_draw_scroll_line_cb();
process_ticks_changed();
}
void DSForLayers::jump_to_value()
{
//Init "jump to value";
m_show_get_jump_value = true;
m_jump_to_value = m_values[m_ctrl.GetActivePos()];
m_imgui->set_requires_extra_frame();
}
void DSForLayers::process_jump_to_value()
{
if (int tick_value = m_ticks.get_tick_from_value(m_jump_to_value, true); tick_value > 0.0) {
m_show_get_jump_value = false;
if (m_ctrl.IsActiveHigherThumb())
SetHigherPos(tick_value);
else
SetLowerPos(tick_value);
}
}
} // DoubleSlider