diff --git a/deps/Boost/Boost.cmake b/deps/Boost/Boost.cmake index ae2bd96466..ec8bab799c 100644 --- a/deps/Boost/Boost.cmake +++ b/deps/Boost/Boost.cmake @@ -28,6 +28,9 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") elseif (MSVC_VERSION LESS 1930) # 1920-1929 = VS 16.0 (v142 toolset) set(_boost_toolset "msvc-14.2") + elseif (MSVC_VERSION LESS 1940) + # 1930-1939 = VS 17.0 (v143 toolset) + set(_boost_toolset "msvc-14.3") else () message(FATAL_ERROR "Unsupported MSVC version") endif () diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index 7288cfe6fa..aba6fcab49 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -15,6 +15,10 @@ elseif (MSVC_VERSION LESS 1930) # 1920-1929 = VS 16.0 (v142 toolset) set(DEP_VS_VER "16") set(DEP_BOOST_TOOLSET "msvc-14.2") +elseif (MSVC_VERSION LESS 1940) +# 1930-1939 = VS 17.0 (v143 toolset) + set(DEP_VS_VER "17") + set(DEP_BOOST_TOOLSET "msvc-14.3") else () message(FATAL_ERROR "Unsupported MSVC version") endif () diff --git a/resources/data/hints.ini b/resources/data/hints.ini index b90976c1a7..ccb248bc37 100644 --- a/resources/data/hints.ini +++ b/resources/data/hints.ini @@ -34,7 +34,7 @@ # # Open preferences (might add item to highlight) # hypertext_type = preferences -# hypertext_preferences_page = 2 (values 0-2 according to prefernces tab to be opened) +# hypertext_preferences_page = name of the prefernces tab # hypertext_preferences_item = show_collapse_button (name of variable saved in prusaslicer.ini connected to the setting in preferences) # # Open gallery (no aditional var) @@ -97,7 +97,7 @@ documentation_link = https://help.prusa3d.com/en/article/reload-from-disk_120427 [hint:Hiding sidebar] text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut Shift+Tab? You can also enable the icon for this from thePreferences. hypertext_type = preferences -hypertext_preferences_page = 2 +hypertext_preferences_page = GUI hypertext_preferences_item = show_collapse_button [hint:Perspective camera] @@ -214,7 +214,7 @@ disabled_tags = SLA [hint:Settings in non-modal window] text = Settings in non-modal window\nDid you know that you can open the Settings in a new non-modal window? This means you can have settings open on one screen and the G-code Preview on the other. Go to thePreferencesand select Settings in non-modal window. hypertext_type = preferences -hypertext_preferences_page = 2 +hypertext_preferences_page = GUI hypertext_preferences_item = dlg_settings_layout_mode [hint:Adaptive infills] diff --git a/resources/icons/edit_button_pressed.svg b/resources/icons/edit_button_pressed.svg new file mode 100644 index 0000000000..6e4058d10f --- /dev/null +++ b/resources/icons/edit_button_pressed.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_colorchanges.svg b/resources/icons/legend_colorchanges.svg new file mode 100644 index 0000000000..cb95ef4676 --- /dev/null +++ b/resources/icons/legend_colorchanges.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_customgcodes.svg b/resources/icons/legend_customgcodes.svg new file mode 100644 index 0000000000..96e0be69e3 --- /dev/null +++ b/resources/icons/legend_customgcodes.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/resources/icons/legend_deretract.svg b/resources/icons/legend_deretract.svg new file mode 100644 index 0000000000..4b636df9de --- /dev/null +++ b/resources/icons/legend_deretract.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_pauseprints.svg b/resources/icons/legend_pauseprints.svg new file mode 100644 index 0000000000..954bc00e97 --- /dev/null +++ b/resources/icons/legend_pauseprints.svg @@ -0,0 +1,76 @@ + + diff --git a/resources/icons/legend_retract.svg b/resources/icons/legend_retract.svg new file mode 100644 index 0000000000..494e2f7286 --- /dev/null +++ b/resources/icons/legend_retract.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_seams.svg b/resources/icons/legend_seams.svg new file mode 100644 index 0000000000..724414119d --- /dev/null +++ b/resources/icons/legend_seams.svg @@ -0,0 +1,45 @@ + + + + + + + diff --git a/resources/icons/legend_shells.svg b/resources/icons/legend_shells.svg new file mode 100644 index 0000000000..b0a93effb2 --- /dev/null +++ b/resources/icons/legend_shells.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_toolchanges.svg b/resources/icons/legend_toolchanges.svg new file mode 100644 index 0000000000..85b6218a9b --- /dev/null +++ b/resources/icons/legend_toolchanges.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/icons/legend_toolmarker.svg b/resources/icons/legend_toolmarker.svg new file mode 100644 index 0000000000..3cd5cf8d96 --- /dev/null +++ b/resources/icons/legend_toolmarker.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/legend_travel.svg b/resources/icons/legend_travel.svg new file mode 100644 index 0000000000..553e90a743 --- /dev/null +++ b/resources/icons/legend_travel.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_wipe.svg b/resources/icons/legend_wipe.svg new file mode 100644 index 0000000000..decfcd6011 --- /dev/null +++ b/resources/icons/legend_wipe.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 09c80a9d9b..db0e54e60d 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -155,6 +155,18 @@ namespace ImGui const wchar_t ClippyMarker = 0x2602; const wchar_t InfoMarker = 0x2603; const wchar_t SliderFloatEditBtnIcon = 0x2604; + const wchar_t SliderFloatEditBtnPressedIcon = 0x2605; + const wchar_t LegendTravel = 0x2606; + const wchar_t LegendWipe = 0x2607; + const wchar_t LegendRetract = 0x2608; + const wchar_t LegendDeretract = 0x2609; + const wchar_t LegendSeams = 0x2610; + const wchar_t LegendToolChanges = 0x2611; + const wchar_t LegendColorChanges = 0x2612; + const wchar_t LegendPausePrints = 0x2613; + const wchar_t LegendCustomGCodes = 0x2614; + const wchar_t LegendShells = 0x2615; + const wchar_t LegendToolMarker = 0x2616; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index deaa0b9241..c79be847be 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -14,7 +14,7 @@ if (TARGET OpenVDB::openvdb) set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp) endif() -add_library(libslic3r STATIC +set(SLIC3R_SOURCES pchheader.cpp pchheader.hpp BoundingBox.cpp @@ -29,6 +29,8 @@ add_library(libslic3r STATIC clipper.hpp ClipperUtils.cpp ClipperUtils.hpp + Color.cpp + Color.hpp Config.cpp Config.hpp EdgeGrid.cpp @@ -291,6 +293,14 @@ add_library(libslic3r STATIC SLA/ReprojectPointsOnMesh.hpp ) +add_library(libslic3r STATIC ${SLIC3R_SOURCES}) + +foreach(_source IN ITEMS ${SLIC3R_SOURCES}) + get_filename_component(_source_path "${_source}" PATH) + string(REPLACE "/" "\\" _group_path "${_source_path}") + source_group("${_group_path}" FILES "${_source}") +endforeach() + if (SLIC3R_STATIC) set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE) endif () diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp new file mode 100644 index 0000000000..7b5a7f3ba7 --- /dev/null +++ b/src/libslic3r/Color.cpp @@ -0,0 +1,400 @@ +#include "libslic3r.h" +#include "Color.hpp" + +#include + +static const float INV_255 = 1.0f / 255.0f; + +namespace Slic3r { + +// Conversion from RGB to HSV color space +// The input RGB values are in the range [0, 1] +// The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1] +static void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v) +{ + assert(0.0f <= r && r <= 1.0f); + assert(0.0f <= g && g <= 1.0f); + assert(0.0f <= b && b <= 1.0f); + + const float max_comp = std::max(std::max(r, g), b); + const float min_comp = std::min(std::min(r, g), b); + const float delta = max_comp - min_comp; + + if (delta > 0.0f) { + if (max_comp == r) + h = 60.0f * (std::fmod(((g - b) / delta), 6.0f)); + else if (max_comp == g) + h = 60.0f * (((b - r) / delta) + 2.0f); + else if (max_comp == b) + h = 60.0f * (((r - g) / delta) + 4.0f); + + s = (max_comp > 0.0f) ? delta / max_comp : 0.0f; + } + else { + h = 0.0f; + s = 0.0f; + } + v = max_comp; + + while (h < 0.0f) { h += 360.0f; } + while (h > 360.0f) { h -= 360.0f; } + + assert(0.0f <= s && s <= 1.0f); + assert(0.0f <= v && v <= 1.0f); + assert(0.0f <= h && h <= 360.0f); +} + +// Conversion from HSV to RGB color space +// The input HSV values are in the ranges h = [0, 360], and s, v = [0, 1] +// The output RGB values are in the range [0, 1] +static void HSVtoRGB(float h, float s, float v, float& r, float& g, float& b) +{ + assert(0.0f <= s && s <= 1.0f); + assert(0.0f <= v && v <= 1.0f); + assert(0.0f <= h && h <= 360.0f); + + const float chroma = v * s; + const float h_prime = std::fmod(h / 60.0f, 6.0f); + const float x = chroma * (1.0f - std::abs(std::fmod(h_prime, 2.0f) - 1.0f)); + const float m = v - chroma; + + if (0.0f <= h_prime && h_prime < 1.0f) { + r = chroma; + g = x; + b = 0.0f; + } + else if (1.0f <= h_prime && h_prime < 2.0f) { + r = x; + g = chroma; + b = 0.0f; + } + else if (2.0f <= h_prime && h_prime < 3.0f) { + r = 0.0f; + g = chroma; + b = x; + } + else if (3.0f <= h_prime && h_prime < 4.0f) { + r = 0.0f; + g = x; + b = chroma; + } + else if (4.0f <= h_prime && h_prime < 5.0f) { + r = x; + g = 0.0f; + b = chroma; + } + else if (5.0f <= h_prime && h_prime < 6.0f) { + r = chroma; + g = 0.0f; + b = x; + } + else { + r = 0.0f; + g = 0.0f; + b = 0.0f; + } + + r += m; + g += m; + b += m; + + assert(0.0f <= r && r <= 1.0f); + assert(0.0f <= g && g <= 1.0f); + assert(0.0f <= b && b <= 1.0f); +} + +class Randomizer +{ + std::random_device m_rd; + +public: + float random_float(float min, float max) { + std::mt19937 rand_generator(m_rd()); + std::uniform_real_distribution distrib(min, max); + return distrib(rand_generator); + } +}; + +ColorRGB::ColorRGB(float r, float g, float b) +: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) }) +{ +} + +ColorRGB::ColorRGB(unsigned char r, unsigned char g, unsigned char b) +: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f) }) +{ +} + +bool ColorRGB::operator < (const ColorRGB& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] < other.m_data[i]) + return true; + } + + return false; +} + +bool ColorRGB::operator > (const ColorRGB& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] > other.m_data[i]) + return true; + } + + return false; +} + +ColorRGB ColorRGB::operator + (const ColorRGB& other) const +{ + ColorRGB ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGB ColorRGB::operator * (float value) const +{ + assert(value >= 0.0f); + ColorRGB ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGBA::ColorRGBA(float r, float g, float b, float a) +: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f), std::clamp(a, 0.0f, 1.0f) }) +{ +} + +ColorRGBA::ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f), std::clamp(a * INV_255, 0.0f, 1.0f) }) +{ +} + +bool ColorRGBA::operator < (const ColorRGBA& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] < other.m_data[i]) + return true; + } + + return false; +} + +bool ColorRGBA::operator > (const ColorRGBA& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] > other.m_data[i]) + return true; + } + + return false; +} + +ColorRGBA ColorRGBA::operator + (const ColorRGBA& other) const +{ + ColorRGBA ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGBA ColorRGBA::operator * (float value) const +{ + assert(value >= 0.0f); + ColorRGBA ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); + } + ret.m_data[3] = this->m_data[3]; + return ret; +} + +ColorRGB operator * (float value, const ColorRGB& other) { return other * value; } +ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; } + +ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t) +{ + assert(0.0f <= t && t <= 1.0f); + return (1.0f - t) * a + t * b; +} + +ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t) +{ + assert(0.0f <= t && t <= 1.0f); + return (1.0f - t) * a + t * b; +} + +ColorRGB complementary(const ColorRGB& color) +{ + return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b() }; +} + +ColorRGBA complementary(const ColorRGBA& color) +{ + return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b(), color.a() }; +} + +ColorRGB saturate(const ColorRGB& color, float factor) +{ + float h, s, v; + RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); + s = std::clamp(s * factor, 0.0f, 1.0f); + float r, g, b; + HSVtoRGB(h, s, v, r, g, b); + return { r, g, b }; +} + +ColorRGBA saturate(const ColorRGBA& color, float factor) +{ + return to_rgba(saturate(to_rgb(color), factor), color.a()); +} + +ColorRGB opposite(const ColorRGB& color) +{ + float h, s, v; + RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); + + h += 65.0f; // 65 instead 60 to avoid circle values + if (h > 360.0f) + h -= 360.0f; + + Randomizer rnd; + s = rnd.random_float(0.65f, 1.0f); + v = rnd.random_float(0.65f, 1.0f); + + float r, g, b; + HSVtoRGB(h, s, v, r, g, b); + return { r, g, b }; +} + +ColorRGB opposite(const ColorRGB& a, const ColorRGB& b) +{ + float ha, sa, va; + RGBtoHSV(a.r(), a.g(), a.b(), ha, sa, va); + float hb, sb, vb; + RGBtoHSV(b.r(), b.g(), b.b(), hb, sb, vb); + + float delta_h = std::abs(ha - hb); + float start_h = (delta_h > 180.0f) ? std::min(ha, hb) : std::max(ha, hb); + + start_h += 5.0f; // to avoid circle change of colors for 120 deg + if (delta_h < 180.0f) + delta_h = 360.0f - delta_h; + + Randomizer rnd; + float out_h = start_h + 0.5f * delta_h; + if (out_h > 360.0f) + out_h -= 360.0f; + float out_s = rnd.random_float(0.65f, 1.0f); + float out_v = rnd.random_float(0.65f, 1.0f); + + float out_r, out_g, out_b; + HSVtoRGB(out_h, out_s, out_v, out_r, out_g, out_b); + return { out_r, out_g, out_b }; +} + +bool can_decode_color(const std::string& color) { return color.size() == 7 && color.front() == '#'; } + +bool decode_color(const std::string& color_in, ColorRGB& color_out) +{ + 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; + }; + + color_out = ColorRGB::BLACK(); + if (can_decode_color(color_in)) { + const char* c = color_in.data() + 1; + for (unsigned int i = 0; i < 3; ++i) { + const int digit1 = hex_digit_to_int(*c++); + const int digit2 = hex_digit_to_int(*c++); + if (digit1 != -1 && digit2 != -1) + color_out.set(i, float(digit1 * 16 + digit2) * INV_255); + } + } + else + return false; + + assert(0.0f <= color_out.r() && color_out.r() <= 1.0f); + assert(0.0f <= color_out.g() && color_out.g() <= 1.0f); + assert(0.0f <= color_out.b() && color_out.b() <= 1.0f); + return true; +} + +bool decode_color(const std::string& color_in, ColorRGBA& color_out) +{ + ColorRGB rgb; + if (!decode_color(color_in, rgb)) + return false; + + color_out = to_rgba(rgb, color_out.a()); + return true; +} + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out) +{ + colors_out = std::vector(colors_in.size(), ColorRGB::BLACK()); + for (size_t i = 0; i < colors_in.size(); ++i) { + if (!decode_color(colors_in[i], colors_out[i])) + return false; + } + return true; +} + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out) +{ + colors_out = std::vector(colors_in.size(), ColorRGBA::BLACK()); + for (size_t i = 0; i < colors_in.size(); ++i) { + if (!decode_color(colors_in[i], colors_out[i])) + return false; + } + return true; +} + +std::string encode_color(const ColorRGB& color) +{ + char buffer[64]; + ::sprintf(buffer, "#%02X%02X%02X", color.r_uchar(), color.g_uchar(), color.b_uchar()); + return std::string(buffer); +} + +std::string encode_color(const ColorRGBA& color) { return encode_color(to_rgb(color)); } + +ColorRGB to_rgb(const ColorRGBA& other_rgba) { return { other_rgba.r(), other_rgba.g(), other_rgba.b() }; } +ColorRGBA to_rgba(const ColorRGB& other_rgb) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), 1.0f }; } +ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), alpha }; } + +ColorRGBA picking_decode(unsigned int id) +{ + return { + float((id >> 0) & 0xff) * INV_255, // red + float((id >> 8) & 0xff) * INV_255, // green + float((id >> 16) & 0xff) * INV_255, // blue + float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff)) * INV_255 // checksum for validating against unwanted alpha blending and multi sampling + }; +} + +unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b) { return r + (g << 8) + (b << 16); } + +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) +{ + // 8 bit hash for the color + unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; + // Increase enthropy by a bit reversal + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + // Flip every second bit to increase the enthropy even more. + b ^= 0x55; + return b; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp new file mode 100644 index 0000000000..fce0c67e00 --- /dev/null +++ b/src/libslic3r/Color.hpp @@ -0,0 +1,170 @@ +#ifndef slic3r_Color_hpp_ +#define slic3r_Color_hpp_ + +#include +#include + +namespace Slic3r { + +class ColorRGB +{ + std::array m_data{1.0f, 1.0f, 1.0f}; + +public: + ColorRGB() = default; + ColorRGB(float r, float g, float b); + ColorRGB(unsigned char r, unsigned char g, unsigned char b); + ColorRGB(const ColorRGB& other) = default; + + ColorRGB& operator = (const ColorRGB& other) { m_data = other.m_data; return *this; } + + bool operator == (const ColorRGB& other) const { return m_data == other.m_data; } + bool operator != (const ColorRGB& other) const { return !operator==(other); } + bool operator < (const ColorRGB& other) const; + bool operator > (const ColorRGB& other) const; + + ColorRGB operator + (const ColorRGB& other) const; + ColorRGB operator * (float value) const; + + const float* const data() const { return m_data.data(); } + + float r() const { return m_data[0]; } + float g() const { return m_data[1]; } + float b() const { return m_data[2]; } + + void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); } + void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } + void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } + + void set(unsigned int comp, float value) { + assert(0 <= comp && comp <= 2); + m_data[comp] = std::clamp(value, 0.0f, 1.0f); + } + + unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); } + + static const ColorRGB BLACK() { return { 0.0f, 0.0f, 0.0f }; } + static const ColorRGB BLUE() { return { 0.0f, 0.0f, 1.0f }; } + static const ColorRGB BLUEISH() { return { 0.5f, 0.5f, 1.0f }; } + static const ColorRGB DARK_GRAY() { return { 0.25f, 0.25f, 0.25f }; } + static const ColorRGB DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f }; } + static const ColorRGB GRAY() { return { 0.5f, 0.5f, 0.5f }; } + static const ColorRGB GREEN() { return { 0.0f, 1.0f, 0.0f }; } + static const ColorRGB GREENISH() { return { 0.5f, 1.0f, 0.5f }; } + static const ColorRGB LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f }; } + static const ColorRGB ORANGE() { return { 0.92f, 0.50f, 0.26f }; } + static const ColorRGB RED() { return { 1.0f, 0.0f, 0.0f }; } + static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; } + static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; } + static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; } + + static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; } + static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; } + static const ColorRGB Z() { return { 0.0f, 0.0f, 0.75f }; } +}; + +class ColorRGBA +{ + std::array m_data{ 1.0f, 1.0f, 1.0f, 1.0f }; + +public: + ColorRGBA() = default; + ColorRGBA(float r, float g, float b, float a); + ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + ColorRGBA(const ColorRGBA& other) = default; + + ColorRGBA& operator = (const ColorRGBA& other) { m_data = other.m_data; return *this; } + + bool operator == (const ColorRGBA& other) const { return m_data == other.m_data; } + bool operator != (const ColorRGBA& other) const { return !operator==(other); } + bool operator < (const ColorRGBA& other) const; + bool operator > (const ColorRGBA& other) const; + + ColorRGBA operator + (const ColorRGBA& other) const; + ColorRGBA operator * (float value) const; + + const float* const data() const { return m_data.data(); } + + float r() const { return m_data[0]; } + float g() const { return m_data[1]; } + float b() const { return m_data[2]; } + float a() const { return m_data[3]; } + + void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); } + void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } + void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } + void a(float a) { m_data[3] = std::clamp(a, 0.0f, 1.0f); } + + void set(unsigned int comp, float value) { + assert(0 <= comp && comp <= 3); + m_data[comp] = std::clamp(value, 0.0f, 1.0f); + } + + unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); } + unsigned char a_uchar() const { return static_cast(m_data[3] * 255.0f); } + + bool is_transparent() const { return m_data[3] < 1.0f; } + + static const ColorRGBA BLACK() { return { 0.0f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA BLUE() { return { 0.0f, 0.0f, 1.0f, 1.0f }; } + static const ColorRGBA BLUEISH() { return { 0.5f, 0.5f, 1.0f, 1.0f }; } + static const ColorRGBA DARK_GRAY() { return { 0.25f, 0.25f, 0.25f, 1.0f }; } + static const ColorRGBA DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f, 1.0f }; } + static const ColorRGBA GRAY() { return { 0.5f, 0.5f, 0.5f, 1.0f }; } + static const ColorRGBA GREEN() { return { 0.0f, 1.0f, 0.0f, 1.0f }; } + static const ColorRGBA GREENISH() { return { 0.5f, 1.0f, 0.5f, 1.0f }; } + static const ColorRGBA LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f, 1.0f }; } + static const ColorRGBA ORANGE() { return { 0.923f, 0.504f, 0.264f, 1.0f }; } + static const ColorRGBA RED() { return { 1.0f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA REDISH() { return { 1.0f, 0.5f, 0.5f, 1.0f }; } + static const ColorRGBA YELLOW() { return { 1.0f, 1.0f, 0.0f, 1.0f }; } + static const ColorRGBA WHITE() { return { 1.0f, 1.0f, 1.0f, 1.0f }; } + + static const ColorRGBA X() { return { 0.75f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA Y() { return { 0.0f, 0.75f, 0.0f, 1.0f }; } + static const ColorRGBA Z() { return { 0.0f, 0.0f, 0.75f, 1.0f }; } +}; + +extern ColorRGB operator * (float value, const ColorRGB& other); +extern ColorRGBA operator * (float value, const ColorRGBA& other); + +extern ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t); +extern ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t); + +extern ColorRGB complementary(const ColorRGB& color); +extern ColorRGBA complementary(const ColorRGBA& color); + +extern ColorRGB saturate(const ColorRGB& color, float factor); +extern ColorRGBA saturate(const ColorRGBA& color, float factor); + +extern ColorRGB opposite(const ColorRGB& color); +extern ColorRGB opposite(const ColorRGB& a, const ColorRGB& b); + +extern bool can_decode_color(const std::string& color); + +extern bool decode_color(const std::string& color_in, ColorRGB& color_out); +extern bool decode_color(const std::string& color_in, ColorRGBA& color_out); + +extern bool decode_colors(const std::vector& colors_in, std::vector& colors_out); +extern bool decode_colors(const std::vector& colors_in, std::vector& colors_out); + +extern std::string encode_color(const ColorRGB& color); +extern std::string encode_color(const ColorRGBA& color); + +extern ColorRGB to_rgb(const ColorRGBA& other_rgba); +extern ColorRGBA to_rgba(const ColorRGB& other_rgb); +extern ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha); + +extern ColorRGBA picking_decode(unsigned int id); +extern unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b); +// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components +// were not interpolated by alpha blending or multi sampling. +extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); + +} // namespace Slic3r + +#endif /* slic3r_Color_hpp_ */ diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index f54e38eec8..1f19a548e7 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -189,6 +189,9 @@ void GCodeProcessor::TimeMachine::reset() max_travel_acceleration = 0.0f; extrude_factor_override_percentage = 1.0f; time = 0.0f; +#if ENABLE_TRAVEL_TIME + travel_time = 0.0f; +#endif // ENABLE_TRAVEL_TIME stop_times = std::vector(); curr.reset(); prev.reset(); @@ -304,9 +307,17 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, floa block_time += additional_time; time += block_time; +#if ENABLE_TRAVEL_TIME + if (block.move_type == EMoveType::Travel) + travel_time += block_time; + else + roles_time[static_cast(block.role)] += block_time; +#endif // ENABLE_TRAVEL_TIME gcode_time.cache += block_time; moves_time[static_cast(block.move_type)] += block_time; +#if !ENABLE_TRAVEL_TIME roles_time[static_cast(block.role)] += block_time; +#endif // !ENABLE_TRAVEL_TIME if (block.layer_id >= layers_time.size()) { const size_t curr_size = layers_time.size(); layers_time.resize(block.layer_id); @@ -1363,6 +1374,18 @@ std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mod return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); } +#if ENABLE_TRAVEL_TIME +float GCodeProcessor::get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].travel_time : 0.0f; +} + +std::string GCodeProcessor::get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].travel_time)) : std::string("N/A"); +} +#endif // ENABLE_TRAVEL_TIME + std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const { std::vector>> ret; @@ -3372,6 +3395,9 @@ void GCodeProcessor::update_estimated_times_stats() auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; data.time = get_time(mode); +#if ENABLE_TRAVEL_TIME + data.travel_time = get_travel_time(mode); +#endif // ENABLE_TRAVEL_TIME data.custom_gcode_times = get_custom_gcode_times(mode, true); data.moves_times = get_moves_time(mode); data.roles_times = get_roles_time(mode); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 2b6ee9cea9..33b9a23f38 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -44,6 +44,9 @@ namespace Slic3r { struct Mode { float time; +#if ENABLE_TRAVEL_TIME + float travel_time; +#endif // ENABLE_TRAVEL_TIME std::vector>> custom_gcode_times; std::vector> moves_times; std::vector> roles_times; @@ -51,6 +54,9 @@ namespace Slic3r { void reset() { time = 0.0f; +#if ENABLE_TRAVEL_TIME + travel_time = 0.0f; +#endif // ENABLE_TRAVEL_TIME custom_gcode_times.clear(); moves_times.clear(); roles_times.clear(); @@ -290,6 +296,9 @@ namespace Slic3r { float max_travel_acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s +#if ENABLE_TRAVEL_TIME + float travel_time; // s +#endif // ENABLE_TRAVEL_TIME struct StopTime { unsigned int g1_line_id; @@ -596,6 +605,10 @@ namespace Slic3r { float get_time(PrintEstimatedStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; +#if ENABLE_TRAVEL_TIME + float get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::string get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; +#endif // ENABLE_TRAVEL_TIME std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 6764b197d1..dd6a2e03cc 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -116,6 +116,8 @@ public: // This type is here to support PresetConfigSubstitutions for physical printers, however it does not belong to the Preset class, // PhysicalPrinter class is used instead. TYPE_PHYSICAL_PRINTER, + // This type is here to support search through the Preferences + TYPE_PREFERENCES, }; Type type = TYPE_INVALID; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2048dc66e9..34a06a18dc 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2753,7 +2753,8 @@ void PrintConfigDef::init_fff_params() def->label = L("Synchronize with object layers"); def->category = L("Support material"); def->tooltip = L("Synchronize support layers with the object print layers. This is useful " - "with multi-material printers, where the extruder switch is expensive."); + "with multi-material printers, where the extruder switch is expensive. " + "This option is only available when top contact Z distance is set to zero."); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index 2243a3c1b5..bc68cd3778 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -3,7 +3,6 @@ #include #include "libslic3r/ExPolygon.hpp" -#include "libslic3r/MTUtils.hpp" // For rasterizing #include diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index 581e84880b..cc9aca0274 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -77,6 +77,8 @@ std::unique_ptr create_raster_grayscale_aa( if (gamma > 0) rst = std::make_unique(res, pxdim, tr, gamma); + else if (std::abs(gamma - 1.) < 1e-6) + rst = std::make_unique(res, pxdim, tr, agg::gamma_none()); else rst = std::make_unique(res, pxdim, tr, agg::gamma_threshold(.5)); diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 1eba360e31..6439830fe8 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -9,7 +9,7 @@ #include #include -#include +//#include namespace Slic3r { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 833146985a..0622bec4e5 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -10,6 +10,8 @@ #include "MTUtils.hpp" #include "Zipper.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" + namespace Slic3r { enum SLAPrintStep : unsigned int { diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index fff1d6b5d4..f267d4f55f 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -49,4 +49,19 @@ #define ENABLE_NEW_CAMERA_MOVEMENTS_SHIFT_SELECTION (1 && ENABLE_2_4_1_RC) +//==================== +// 2.5.0.alpha1 techs +//==================== +#define ENABLE_2_5_0_ALPHA1 1 + +// Enable changes in preview layout +#define ENABLE_PREVIEW_LAYOUT (1 && ENABLE_2_5_0_ALPHA1) +// Enable drawing the items in legend toolbar using icons +#define ENABLE_LEGEND_TOOLBAR_ICONS (1 && ENABLE_PREVIEW_LAYOUT) +// Enable coloring of toolpaths in preview by layer time +#define ENABLE_PREVIEW_LAYER_TIME (1 && ENABLE_2_5_0_ALPHA1) +// Enable showing time estimate for travel moves in legend +#define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1) + + #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index d9419495ee..2562d1913a 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -360,7 +361,7 @@ inline std::string get_time_dhms(float time_in_secs) else if (minutes > 0) ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs); else - ::sprintf(buffer, "%ds", (int)time_in_secs); + ::sprintf(buffer, "%ds", (int)std::round(time_in_secs)); return buffer; } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 34cc980513..34c0efd014 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -169,9 +169,11 @@ set(SLIC3R_GUI_SOURCES GUI/PrintHostDialogs.cpp GUI/PrintHostDialogs.hpp GUI/Jobs/Job.hpp - GUI/Jobs/Job.cpp - GUI/Jobs/PlaterJob.hpp - GUI/Jobs/PlaterJob.cpp + GUI/Jobs/Worker.hpp + GUI/Jobs/BoostThreadWorker.hpp + GUI/Jobs/BoostThreadWorker.cpp + GUI/Jobs/BusyCursorJob.hpp + GUI/Jobs/PlaterWorker.hpp GUI/Jobs/ArrangeJob.hpp GUI/Jobs/ArrangeJob.cpp GUI/Jobs/RotoptimizeJob.hpp @@ -183,6 +185,8 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/ProgressIndicator.hpp GUI/Jobs/NotificationProgressIndicator.hpp GUI/Jobs/NotificationProgressIndicator.cpp + GUI/Jobs/ThreadSafeQueue.hpp + GUI/Jobs/SLAImportDialog.hpp GUI/ProgressStatusBar.hpp GUI/ProgressStatusBar.cpp GUI/Mouse3DController.cpp @@ -258,6 +262,12 @@ endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) +foreach(_source IN ITEMS ${SLIC3R_GUI_SOURCES}) + get_filename_component(_source_path "${_source}" PATH) + string(REPLACE "/" "\\" _group_path "${_source_path}") + source_group("${_group_path}" FILES "${_source}") +endforeach() + encoding_check(libslic3r_gui) target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES}) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index bd5ca9f521..21e23d402e 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -19,8 +19,10 @@ #include static const float GROUND_Z = -0.02f; -static const std::array DEFAULT_MODEL_COLOR = { 0.235f, 0.235f, 0.235f, 1.0f }; -static const std::array PICKING_MODEL_COLOR = { 0.0f, 0.0f, 0.0f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_MODEL_COLOR = Slic3r::ColorRGBA::DARK_GRAY(); +static const Slic3r::ColorRGBA PICKING_MODEL_COLOR = Slic3r::ColorRGBA::BLACK(); +static const Slic3r::ColorRGBA DEFAULT_SOLID_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_TRANSPARENT_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 0.6f }; namespace Slic3r { namespace GUI { @@ -121,15 +123,15 @@ void Bed3D::Axes::render() const shader->set_uniform("emission_factor", 0.0f); // x axis - const_cast(&m_arrow)->set_color(-1, { 0.75f, 0.0f, 0.0f, 1.0f }); + const_cast(&m_arrow)->set_color(-1, ColorRGBA::X()); render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); // y axis - const_cast(&m_arrow)->set_color(-1, { 0.0f, 0.75f, 0.0f, 1.0f }); + const_cast(&m_arrow)->set_color(-1, ColorRGBA::Y()); render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); // z axis - const_cast(&m_arrow)->set_color(-1, { 0.0f, 0.0f, 0.75f, 1.0f }); + const_cast(&m_arrow)->set_color(-1, ColorRGBA::Z()); render_axis(Geometry::assemble_transform(m_origin).cast()); shader->stop_using(); @@ -550,10 +552,7 @@ void Bed3D::render_default(bool bottom, bool picking) const if (!picking) { // draw grid glsafe(::glLineWidth(1.5f * m_scale_factor)); - if (has_model && !bottom) - glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 1.0f)); - else - glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.6f)); + glsafe(::glColor4fv(has_model && !bottom ? DEFAULT_SOLID_GRID_COLOR.data() : DEFAULT_TRANSPARENT_GRID_COLOR.data())); glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count())); } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 263ba0e73a..93f0548bcb 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -344,20 +344,20 @@ void GLVolume::SinkingContours::update() m_model.reset(); } -const std::array GLVolume::SELECTED_COLOR = { 0.0f, 1.0f, 0.0f, 1.0f }; -const std::array GLVolume::HOVER_SELECT_COLOR = { 0.4f, 0.9f, 0.1f, 1.0f }; -const std::array GLVolume::HOVER_DESELECT_COLOR = { 1.0f, 0.75f, 0.75f, 1.0f }; -const std::array GLVolume::OUTSIDE_COLOR = { 0.0f, 0.38f, 0.8f, 1.0f }; -const std::array GLVolume::SELECTED_OUTSIDE_COLOR = { 0.19f, 0.58f, 1.0f, 1.0f }; -const std::array GLVolume::DISABLED_COLOR = { 0.25f, 0.25f, 0.25f, 1.0f }; -const std::array GLVolume::SLA_SUPPORT_COLOR = { 0.75f, 0.75f, 0.75f, 1.0f }; -const std::array GLVolume::SLA_PAD_COLOR = { 0.0f, 0.2f, 0.0f, 1.0f }; -const std::array GLVolume::NEUTRAL_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f }; -const std::array, 4> GLVolume::MODEL_COLOR = { { - { 1.0f, 1.0f, 0.0f, 1.f }, - { 1.0f, 0.5f, 0.5f, 1.f }, - { 0.5f, 1.0f, 0.5f, 1.f }, - { 0.5f, 0.5f, 1.0f, 1.f } +const ColorRGBA GLVolume::SELECTED_COLOR = ColorRGBA::GREEN(); +const ColorRGBA GLVolume::HOVER_SELECT_COLOR = { 0.4f, 0.9f, 0.1f, 1.0f }; +const ColorRGBA GLVolume::HOVER_DESELECT_COLOR = { 1.0f, 0.75f, 0.75f, 1.0f }; +const ColorRGBA GLVolume::OUTSIDE_COLOR = { 0.0f, 0.38f, 0.8f, 1.0f }; +const ColorRGBA GLVolume::SELECTED_OUTSIDE_COLOR = { 0.19f, 0.58f, 1.0f, 1.0f }; +const ColorRGBA GLVolume::DISABLED_COLOR = ColorRGBA::DARK_GRAY(); +const ColorRGBA GLVolume::SLA_SUPPORT_COLOR = ColorRGBA::LIGHT_GRAY(); +const ColorRGBA GLVolume::SLA_PAD_COLOR = { 0.0f, 0.2f, 0.0f, 1.0f }; +const ColorRGBA GLVolume::NEUTRAL_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f }; +const std::array GLVolume::MODEL_COLOR = { { + ColorRGBA::YELLOW(), + { 1.0f, 0.5f, 0.5f, 1.0f }, + { 0.5f, 1.0f, 0.5f, 1.0f }, + { 0.5f, 0.5f, 1.0f, 1.0f } } }; GLVolume::GLVolume(float r, float g, float b, float a) @@ -388,21 +388,6 @@ GLVolume::GLVolume(float r, float g, float b, float a) set_render_color(color); } -void GLVolume::set_color(const std::array& rgba) -{ - color = rgba; -} - -void GLVolume::set_render_color(float r, float g, float b, float a) -{ - render_color = { r, g, b, a }; -} - -void GLVolume::set_render_color(const std::array& rgba) -{ - render_color = rgba; -} - void GLVolume::set_render_color() { bool outside = is_outside || is_below_printbed(); @@ -432,40 +417,28 @@ void GLVolume::set_render_color() set_render_color(color); } - if (!printable) { - render_color[0] /= 4; - render_color[1] /= 4; - render_color[2] /= 4; - } + if (!printable) + render_color = saturate(render_color, 0.25f); if (force_transparent) - render_color[3] = color[3]; + render_color.a(color.a()); } -std::array color_from_model_volume(const ModelVolume& model_volume) +ColorRGBA color_from_model_volume(const ModelVolume& model_volume) { - std::array color; - if (model_volume.is_negative_volume()) { - color[0] = 0.2f; - color[1] = 0.2f; - color[2] = 0.2f; - } - else if (model_volume.is_modifier()) { - color[0] = 1.0f; - color[1] = 1.0f; - color[2] = 0.2f; - } - else if (model_volume.is_support_blocker()) { - color[0] = 1.0f; - color[1] = 0.2f; - color[2] = 0.2f; - } - else if (model_volume.is_support_enforcer()) { - color[0] = 0.2f; - color[1] = 0.2f; - color[2] = 1.0f; - } - color[3] = model_volume.is_model_part() ? 1.f : 0.5f; + ColorRGBA color; + if (model_volume.is_negative_volume()) + color = { 0.2f, 0.2f, 0.2f, 1.0f }; + else if (model_volume.is_modifier()) + color = { 1.0, 1.0f, 0.2f, 1.0f }; + else if (model_volume.is_support_blocker()) + color = { 1.0f, 0.2f, 0.2f, 1.0f }; + else if (model_volume.is_support_enforcer()) + color = { 0.2f, 0.2f, 1.0f, 1.0f }; + + if (!model_volume.is_model_part()) + color.a(0.5f); + return color; } @@ -625,8 +598,8 @@ int GLVolumeCollection::load_object_volume( const int extruder_id = model_volume->extruder_id(); const ModelInstance *instance = model_object->instances[instance_idx]; const TriangleMesh &mesh = model_volume->mesh(); - std::array color = GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4]; - color[3] = model_volume->is_model_part() ? 1.f : 0.5f; + ColorRGBA color = GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4]; + color.a(model_volume->is_model_part() ? 1.0f : 0.5f); this->volumes.emplace_back(new GLVolume(color)); GLVolume& v = *this->volumes.back(); v.set_color(color_from_model_volume(*model_volume)); @@ -707,13 +680,13 @@ int GLVolumeCollection::load_wipe_tower_preview( height = 0.1f; TriangleMesh mesh; - std::array color = { 0.5f, 0.5f, 0.0f, 1.0f }; + ColorRGBA color = ColorRGBA::DARK_YELLOW(); // In case we don't know precise dimensions of the wipe tower yet, we'll draw // the box with different color with one side jagged: if (size_unknown) { - color[0] = 0.9f; - color[1] = 0.6f; + color.r(0.9f); + color.g(0.6f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway. depth = std::max(depth, 10.f); @@ -769,14 +742,14 @@ int GLVolumeCollection::load_wipe_tower_preview( return int(volumes.size() - 1); } -GLVolume* GLVolumeCollection::new_toolpath_volume(const std::array& rgba, size_t reserve_vbo_floats) +GLVolume* GLVolumeCollection::new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats) { GLVolume *out = new_nontoolpath_volume(rgba, reserve_vbo_floats); out->is_extrusion_path = true; return out; } -GLVolume* GLVolumeCollection::new_nontoolpath_volume(const std::array& rgba, size_t reserve_vbo_floats) +GLVolume* GLVolumeCollection::new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats) { GLVolume *out = new GLVolume(rgba); out->is_extrusion_path = false; @@ -793,7 +766,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i) { GLVolume* volume = volumes[i]; - bool is_transparent = (volume->render_color[3] < 1.0f); + bool is_transparent = volume->render_color.is_transparent(); if (((type == GLVolumeCollection::ERenderType::Opaque && !is_transparent) || (type == GLVolumeCollection::ERenderType::Transparent && is_transparent) || type == GLVolumeCollection::ERenderType::All) && @@ -972,8 +945,7 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo void GLVolumeCollection::reset_outside_state() { - for (GLVolume* volume : this->volumes) - { + for (GLVolume* volume : this->volumes) { if (volume != nullptr) volume->is_outside = false; } @@ -981,46 +953,18 @@ void GLVolumeCollection::reset_outside_state() void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* config) { - static const float inv_255 = 1.0f / 255.0f; + using ColorItem = std::pair; + std::vector colors; - struct Color - { - std::string text; - unsigned char rgb[3]; - - Color() - : text("") - { - rgb[0] = 255; - rgb[1] = 255; - rgb[2] = 255; - } - - void set(const std::string& text, unsigned char* rgb) - { - this->text = text; - ::memcpy((void*)this->rgb, (const void*)rgb, 3 * sizeof(unsigned char)); - } - }; - - if (config == nullptr) - return; - - unsigned char rgb[3]; - std::vector colors; - - if (static_cast(config->opt_int("printer_technology")) == ptSLA) - { + if (static_cast(config->opt_int("printer_technology")) == ptSLA) { const std::string& txt_color = config->opt_string("material_colour").empty() ? print_config_def.get("material_colour")->get_default_value()->value : config->opt_string("material_colour"); - if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) { - colors.resize(1); - colors[0].set(txt_color, rgb); - } - } - else - { + ColorRGBA rgba; + if (decode_color(txt_color, rgba)) + colors.push_back({ txt_color, rgba }); +} + else { const ConfigOptionStrings* extruders_opt = dynamic_cast(config->option("extruder_colour")); if (extruders_opt == nullptr) return; @@ -1029,37 +973,35 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con if (filamemts_opt == nullptr) return; - unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size()); + size_t colors_count = std::max(extruders_opt->values.size(), filamemts_opt->values.size()); if (colors_count == 0) return; colors.resize(colors_count); for (unsigned int i = 0; i < colors_count; ++i) { - const std::string& txt_color = config->opt_string("extruder_colour", i); - if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) - colors[i].set(txt_color, rgb); + const std::string& ext_color = config->opt_string("extruder_colour", i); + ColorRGBA rgba; + if (decode_color(ext_color, rgba)) + colors[i] = { ext_color, rgba }; else { - const std::string& txt_color = config->opt_string("filament_colour", i); - if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) - colors[i].set(txt_color, rgb); + const std::string& fil_color = config->opt_string("filament_colour", i); + if (decode_color(fil_color, rgba)) + colors[i] = { fil_color, rgba }; } } } for (GLVolume* volume : volumes) { - if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) + if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0) continue; int extruder_id = volume->extruder_id - 1; if (extruder_id < 0 || (int)colors.size() <= extruder_id) extruder_id = 0; - const Color& color = colors[extruder_id]; - if (!color.text.empty()) { - for (int i = 0; i < 3; ++i) { - volume->color[i] = (float)color.rgb[i] * inv_255; - } - } + const ColorItem& color = colors[extruder_id]; + if (!color.first.empty()) + volume->color = color.second; } } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 6d82e3bb7c..7ad12c3542 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -7,6 +7,7 @@ #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/Color.hpp" #include "GLModel.hpp" @@ -43,7 +44,7 @@ class ModelVolume; enum ModelInstanceEPrintVolumeState : unsigned char; // Return appropriate color based on the ModelVolume. -std::array color_from_model_volume(const ModelVolume& model_volume); +extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume); // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. @@ -248,16 +249,16 @@ private: class GLVolume { public: - static const std::array SELECTED_COLOR; - static const std::array HOVER_SELECT_COLOR; - static const std::array HOVER_DESELECT_COLOR; - static const std::array OUTSIDE_COLOR; - static const std::array SELECTED_OUTSIDE_COLOR; - static const std::array DISABLED_COLOR; - static const std::array SLA_SUPPORT_COLOR; - static const std::array SLA_PAD_COLOR; - static const std::array NEUTRAL_COLOR; - static const std::array, 4> MODEL_COLOR; + static const ColorRGBA SELECTED_COLOR; + static const ColorRGBA HOVER_SELECT_COLOR; + static const ColorRGBA HOVER_DESELECT_COLOR; + static const ColorRGBA OUTSIDE_COLOR; + static const ColorRGBA SELECTED_OUTSIDE_COLOR; + static const ColorRGBA DISABLED_COLOR; + static const ColorRGBA SLA_SUPPORT_COLOR; + static const ColorRGBA SLA_PAD_COLOR; + static const ColorRGBA NEUTRAL_COLOR; + static const std::array MODEL_COLOR; enum EHoverState : unsigned char { @@ -267,8 +268,8 @@ public: HS_Deselect }; - GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); - GLVolume(const std::array& rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} + GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f); + GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {} private: Geometry::Transformation m_instance_transformation; @@ -305,9 +306,9 @@ private: public: // Color of the triangles / quads held by this volume. - std::array color; + ColorRGBA color; // Color used to render this volume. - std::array render_color; + ColorRGBA render_color; struct CompositeID { CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {} @@ -393,9 +394,8 @@ public: return out; } - void set_color(const std::array& rgba); - void set_render_color(float r, float g, float b, float a); - void set_render_color(const std::array& rgba); + void set_color(const ColorRGBA& rgba) { color = rgba; } + void set_render_color(const ColorRGBA& rgba) { render_color = rgba; } // Sets render color in dependence of current state void set_render_color(); // set color according to model volume @@ -595,8 +595,8 @@ public: int load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); - GLVolume* new_toolpath_volume(const std::array& rgba, size_t reserve_vbo_floats = 0); - GLVolume* new_nontoolpath_volume(const std::array& rgba, size_t reserve_vbo_floats = 0); + GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); + GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); // Render the volumes by OpenGL. void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 2b21b9ee01..e444fb03c4 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -2,6 +2,7 @@ #include "I18N.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" @@ -133,12 +134,12 @@ wxString CopyrightsDialog::get_html_text() wxColour bgr_clr = wxGetApp().get_window_default_clr();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); const auto text_clr = wxGetApp().get_label_clr_default(); - const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); - const wxString copyright_str = _(L("Copyright")) + "© "; + const wxString copyright_str = _L("Copyright") + "© "; // TRN "Slic3r _is licensed under the_ License" - const wxString header_str = _(L("License agreements of all following programs (libraries) are part of application license agreement")); + const wxString header_str = _L("License agreements of all following programs (libraries) are part of application license agreement"); wxString text = wxString::Format( "" @@ -257,8 +258,8 @@ AboutDialog::AboutDialog() m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit())); wxFont font = get_default_font(this); const auto text_clr = wxGetApp().get_label_clr_default(); - auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); const int fs = font.GetPointSize()-1; int size[] = {fs,fs,fs,fs,fs,fs,fs}; diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 39ba849d33..e23591fb62 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -395,21 +395,5 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); } -bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out) -{ - rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; - if (scolor.size() != 7 || scolor.front() != '#') - return false; - const char* c = scolor.data() + 1; - for (size_t i = 0; i < 3; ++i) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - return false; - rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); - } - return true; -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 4d1d383c41..5af90c5f7b 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -9,9 +9,12 @@ #include #endif +#include "libslic3r/Color.hpp" + struct NSVGimage; -namespace Slic3r { namespace GUI { +namespace Slic3r { +namespace GUI { class BitmapCache { @@ -43,11 +46,9 @@ public: wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = ""); wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false); - wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3], bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } + wxBitmap mksolid(size_t width, size_t height, const ColorRGB& rgb, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } - static bool parse_color(const std::string& scolor, unsigned char* rgb_out); - private: std::map m_map; double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs) diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index afa495d0c2..e2bea55d13 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -5,6 +5,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Time.hpp" +#include "libslic3r/Color.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" #include "wxExtensions.hpp" @@ -31,11 +32,9 @@ static wxString format_reason(const Config::Snapshot::Reason reason) static std::string get_color(wxColour colour) { - wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - return clr_str.ToStdString(); + return encode_color(ColorRGB(colour.Red(), colour.Green(), colour.Blue())); }; - static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active, bool dark_mode) { // Start by declaring a row with an alternating background color. diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index dadf5d8ca3..4ff0898820 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -36,6 +36,7 @@ #include "libslic3r/Config.hpp" #include "libslic3r/libslic3r.h" #include "libslic3r/Model.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" @@ -746,9 +747,9 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector* are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); wxString text; if (all_printers) { @@ -2522,23 +2523,33 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese { wxString header, caption = _L("Configuration is edited in ConfigWizard"); const auto enabled_vendors = appconfig_new.vendors(); + const auto enabled_vendors_old = app_config->vendors(); bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); PrinterTechnology preferred_pt = ptAny; - auto get_preferred_printer_technology = [enabled_vendors, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { + auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { const auto config = enabled_vendors.find(bundle_name); PrinterTechnology pt = ptAny; if (config != enabled_vendors.end()) { for (const auto& model : bundle.vendor_profile->models) { if (const auto model_it = config->second.find(model.id); model_it != config->second.end() && model_it->second.size() > 0) { - if (pt == ptAny) - pt = model.technology; - // if preferred printer model has SLA printer technology it's important to check the model for multypart state - if (pt == ptSLA && suppress_sla_printer) - continue; - else + pt = model.technology; + const auto config_old = enabled_vendors_old.find(bundle_name); + if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; return pt; + } + + if (const auto model_it_old = config_old->second.find(model.id); + model_it_old == config_old->second.end() || model_it_old->second != model_it->second) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; + return pt; + } } } } @@ -2645,7 +2656,6 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese std::string preferred_model; std::string preferred_variant; - const auto enabled_vendors_old = app_config->vendors(); auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { const auto config = enabled_vendors.find(bundle_name); if (config == enabled_vendors.end()) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 3d914d5b66..ee5aacb191 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -2557,36 +2557,46 @@ bool Control::check_ticks_changed_event(Type type) std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder) { + auto opposite_one_color = [](const std::string& color) { + ColorRGB rgb; + decode_color(color, rgb); + return encode_color(opposite(rgb)); + }; + auto opposite_two_colors = [](const std::string& a, const std::string& b) { + ColorRGB rgb1; decode_color(a, rgb1); + ColorRGB rgb2; decode_color(b, rgb2); + return encode_color(opposite(rgb1, rgb2)); + }; + if (mode == SingleExtruder && type == ColorChange && m_use_default_colors) { #if 1 if (ticks.empty()) - return color_generator.get_opposite_color((*m_colors)[0]); - + return opposite_one_color((*m_colors)[0]); + auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick); - if (before_tick_it == ticks.end()) - { + if (before_tick_it == ticks.end()) { while (before_tick_it != ticks.begin()) if (--before_tick_it; before_tick_it->type == ColorChange) break; if (before_tick_it->type == ColorChange) - return color_generator.get_opposite_color(before_tick_it->color); - return color_generator.get_opposite_color((*m_colors)[0]); + return opposite_one_color(before_tick_it->color); + + return opposite_one_color((*m_colors)[0]); } - if (before_tick_it == ticks.begin()) - { + if (before_tick_it == ticks.begin()) { const std::string& frst_color = (*m_colors)[0]; if (before_tick_it->type == ColorChange) - return color_generator.get_opposite_color(frst_color, before_tick_it->color); + return opposite_two_colors(frst_color, before_tick_it->color); auto next_tick_it = before_tick_it; while (next_tick_it != ticks.end()) if (++next_tick_it; next_tick_it->type == ColorChange) break; if (next_tick_it->type == ColorChange) - return color_generator.get_opposite_color(frst_color, next_tick_it->color); + return opposite_two_colors(frst_color, next_tick_it->color); - return color_generator.get_opposite_color(frst_color); + return opposite_one_color(frst_color); } std::string frst_color = ""; @@ -2607,13 +2617,15 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int if (before_tick_it->type == ColorChange) { if (frst_color.empty()) - return color_generator.get_opposite_color(before_tick_it->color); - return color_generator.get_opposite_color(before_tick_it->color, frst_color); + return opposite_one_color(before_tick_it->color); + + return opposite_two_colors(before_tick_it->color, frst_color); } if (frst_color.empty()) - return color_generator.get_opposite_color((*m_colors)[0]); - return color_generator.get_opposite_color((*m_colors)[0], frst_color); + return opposite_one_color((*m_colors)[0]); + + return opposite_two_colors((*m_colors)[0], frst_color); #else const std::vector& colors = ColorPrintColors::get(); if (ticks.empty()) diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 23275cf2ad..e0f713d879 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -3,7 +3,6 @@ #include "libslic3r/CustomGCode.hpp" #include "wxExtensions.hpp" -#include "DoubleSlider_Utils.hpp" #include #include @@ -119,7 +118,6 @@ class TickCodeInfo // int m_default_color_idx = 0; std::vector* m_colors {nullptr}; - ColorGenerator color_generator; std::string get_color_for_tick(TickCode tick, Type type, const int extruder); diff --git a/src/slic3r/GUI/DoubleSlider_Utils.hpp b/src/slic3r/GUI/DoubleSlider_Utils.hpp index b5955f2fc4..283d527fa4 100644 --- a/src/slic3r/GUI/DoubleSlider_Utils.hpp +++ b/src/slic3r/GUI/DoubleSlider_Utils.hpp @@ -1,191 +1,8 @@ +#ifndef slic3r_GUI_DoubleSlider_Utils_hpp_ +#define slic3r_GUI_DoubleSlider_Utils_hpp_ + #include #include -#include "wx/colour.h" -class ColorGenerator -{ - // Some of next code is borrowed from https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both - typedef struct { - double r; // a fraction between 0 and 1 - double g; // a fraction between 0 and 1 - double b; // a fraction between 0 and 1 - } rgb; - - typedef struct { - double h; // angle in degrees - double s; // a fraction between 0 and 1 - double v; // a fraction between 0 and 1 - } hsv; - - //static hsv rgb2hsv(rgb in); - //static rgb hsv2rgb(hsv in); - - hsv rgb2hsv(rgb in) - { - hsv out; - double min, max, delta; - - min = in.r < in.g ? in.r : in.g; - min = min < in.b ? min : in.b; - - max = in.r > in.g ? in.r : in.g; - max = max > in.b ? max : in.b; - - out.v = max; // v - delta = max - min; - if (delta < 0.00001) - { - out.s = 0; - out.h = 0; // undefined, maybe nan? - return out; - } - if (max > 0.0) { // NOTE: if Max is == 0, this divide would cause a crash - out.s = (delta / max); // s - } - else { - // if max is 0, then r = g = b = 0 - // s = 0, h is undefined - out.s = 0.0; - out.h = NAN; // its now undefined - return out; - } - if (in.r >= max) // > is bogus, just keeps compilor happy - out.h = (in.g - in.b) / delta; // between yellow & magenta - else - if (in.g >= max) - out.h = 2.0 + (in.b - in.r) / delta; // between cyan & yellow - else - out.h = 4.0 + (in.r - in.g) / delta; // between magenta & cyan - - out.h *= 60.0; // degrees - - if (out.h < 0.0) - out.h += 360.0; - - return out; - } - - hsv rgb2hsv(const std::string& str_clr_in) - { - wxColour clr(str_clr_in); - rgb in = { clr.Red() / 255.0, clr.Green() / 255.0, clr.Blue() / 255.0 }; - return rgb2hsv(in); - } - - - rgb hsv2rgb(hsv in) - { - double hh, p, q, t, ff; - long i; - rgb out; - - if (in.s <= 0.0) { // < is bogus, just shuts up warnings - out.r = in.v; - out.g = in.v; - out.b = in.v; - return out; - } - hh = in.h; - if (hh >= 360.0) hh -= 360.0;//hh = 0.0; - hh /= 60.0; - i = (long)hh; - ff = hh - i; - p = in.v * (1.0 - in.s); - q = in.v * (1.0 - (in.s * ff)); - t = in.v * (1.0 - (in.s * (1.0 - ff))); - - switch (i) { - case 0: - out.r = in.v; - out.g = t; - out.b = p; - break; - case 1: - out.r = q; - out.g = in.v; - out.b = p; - break; - case 2: - out.r = p; - out.g = in.v; - out.b = t; - break; - - case 3: - out.r = p; - out.g = q; - out.b = in.v; - break; - case 4: - out.r = t; - out.g = p; - out.b = in.v; - break; - case 5: - default: - out.r = in.v; - out.g = p; - out.b = q; - break; - } - return out; - } - - std::random_device rd; - -public: - - ColorGenerator() {} - ~ColorGenerator() {} - - double rand_val() - { - std::mt19937 rand_generator(rd()); - - // this value will be used for Saturation and Value - // to avoid extremely light/dark colors, take this value from range [0.65; 1.0] - std::uniform_real_distribution distrib(0.65, 1.0); - return distrib(rand_generator); - } - - - std::string get_opposite_color(const std::string& color) - { - std::string opp_color = ""; - - hsv hsv_clr = rgb2hsv(color); - hsv_clr.h += 65; // 65 instead 60 to avoid circle values - hsv_clr.s = rand_val(); - hsv_clr.v = rand_val(); - - rgb rgb_opp_color = hsv2rgb(hsv_clr); - - wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), (unsigned char)(rgb_opp_color.r * 255), (unsigned char)(rgb_opp_color.g * 255), (unsigned char)(rgb_opp_color.b * 255)); - opp_color = clr_str.ToStdString(); - - return opp_color; - } - - std::string get_opposite_color(const std::string& color_frst, const std::string& color_scnd) - { - std::string opp_color = ""; - - hsv hsv_frst = rgb2hsv(color_frst); - hsv hsv_scnd = rgb2hsv(color_scnd); - - double delta_h = fabs(hsv_frst.h - hsv_scnd.h); - double start_h = delta_h > 180 ? std::min(hsv_scnd.h, hsv_frst.h) : std::max(hsv_scnd.h, hsv_frst.h); - start_h += 5; // to avoid circle change of colors for 120 deg - if (delta_h < 180) - delta_h = 360 - delta_h; - - hsv hsv_opp = hsv{ start_h + 0.5 * delta_h, rand_val(), rand_val() }; - rgb rgb_opp_color = hsv2rgb(hsv_opp); - - wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), (unsigned char)(rgb_opp_color.r * 255), (unsigned char)(rgb_opp_color.g * 255), (unsigned char)(rgb_opp_color.b * 255)); - opp_color = clr_str.ToStdString(); - - return opp_color; - } -}; \ No newline at end of file +#endif // slic3r_GUI_DoubleSlider_Utils_hpp_ diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 5ec622b872..c54e3e80b1 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1387,13 +1387,8 @@ void ColourPicker::set_value(const boost::any& value, bool change_event) boost::any& ColourPicker::get_value() { auto colour = static_cast(window)->GetColour(); - if (colour == wxTransparentColour) - m_value = std::string(""); - else { - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - m_value = clr_str.ToStdString(); - } - return m_value; + m_value = (colour == wxTransparentColour) ? std::string("") : encode_color(ColorRGB(colour.Red(), colour.Green(), colour.Blue())); + return m_value; } void ColourPicker::msw_rescale() diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 151e66d4d1..5edc321f7b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -47,32 +47,6 @@ static EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(EMoveType::Retract) + id); } -static std::array decode_color(const std::string& color) { - static const float INV_255 = 1.0f / 255.0f; - - std::array ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - const char* c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - ret[j] = float(digit1 * 16 + digit2) * INV_255; - } - } - return ret; -} - -static std::vector> decode_colors(const std::vector& colors) { - std::vector> 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) @@ -186,27 +160,47 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); } -GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const +#if ENABLE_PREVIEW_LAYER_TIME +float GCodeViewer::Extrusions::Range::step_size(EType type) const +{ + switch (type) + { + default: + case EType::Linear: { return (max > min) ? (max - min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } + case EType::Logarithmic: { return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } + } +} + +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const +#else +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const +#endif // ENABLE_PREVIEW_LAYER_TIME { // Input value scaled to the colors range +#if ENABLE_PREVIEW_LAYER_TIME + 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; } + } + } +#else const float step = step_size(); const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f +#endif // ENABLE_PREVIEW_LAYER_TIME 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(static_cast(global_t), 0, color_max_idx); + const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); const size_t color_high_idx = std::clamp(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(color_low_idx), 0.0f, 1.0f); - // Interpolate between the low and high colors to find exactly which color the input value should get - Color ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - for (unsigned int i = 0; i < 3; ++i) { - ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); - } - return ret; + return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast(color_low_idx)); } GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { @@ -351,11 +345,11 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, u return ret; }; - static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; + static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK; - static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; - static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; - static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; + static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; + static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; + static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0) return; @@ -480,7 +474,7 @@ void GCodeViewer::SequentialView::render(float legend_height) const gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); } -const std::vector GCodeViewer::Extrusion_Role_Colors {{ +const std::vector 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 @@ -499,7 +493,7 @@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed }}; -const std::vector GCodeViewer::Options_Colors {{ +const std::vector 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 @@ -509,7 +503,7 @@ const std::vector GCodeViewer::Options_Colors {{ { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes }}; -const std::vector GCodeViewer::Travel_Colors {{ +const std::vector 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 @@ -517,7 +511,7 @@ const std::vector GCodeViewer::Travel_Colors {{ #if 1 // Normal ranges -const std::vector GCodeViewer::Range_Colors {{ +const std::vector 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 }, @@ -532,7 +526,7 @@ const std::vector GCodeViewer::Range_Colors {{ }}; #else // Detailed ranges -const std::vector GCodeViewer::Range_Colors{ { +const std::vector GCodeViewer::Range_Colors{ { { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish { 0.5f * (0.043f + 0.075f), 0.5f * (0.173f + 0.349f), 0.5f * (0.478f + 0.522f), 1.0f }, { 0.075f, 0.349f, 0.522f, 1.0f }, @@ -557,8 +551,8 @@ const std::vector GCodeViewer::Range_Colors{ { } }; #endif -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 }; +const ColorRGBA GCodeViewer::Wipe_Color = ColorRGBA::YELLOW(); +const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); GCodeViewer::GCodeViewer() { @@ -729,14 +723,17 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) // update tool colors from config stored in the gcode - m_tool_colors = decode_colors(gcode_result.extruder_colors); + decode_colors(gcode_result.extruder_colors, m_tool_colors); else // update tool colors - m_tool_colors = decode_colors(str_tool_colors); + decode_colors(str_tool_colors, m_tool_colors); + + ColorRGBA default_color; + decode_color("#FF8000", default_color); // ensure there are enough colors defined while (m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) - m_tool_colors.push_back(decode_color("#FF8000")); + m_tool_colors.push_back(default_color); // update ranges for coloring / legend m_extrusions.reset_ranges(); @@ -770,19 +767,37 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v } } +#if ENABLE_PREVIEW_LAYER_TIME + for (size_t i = 0; i < gcode_result.print_statistics.modes.size(); ++i) { + m_layers_times[i] = gcode_result.print_statistics.modes[i].layers_times; + } + + for (size_t i = 0; i < m_layers_times.size(); ++i) { + for (float time : m_layers_times[i]) { + m_extrusions.ranges.layer_time[i].update_from(time); + } + } +#endif // ENABLE_PREVIEW_LAYER_TIME + #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS // update buffers' render paths +#if ENABLE_PREVIEW_LAYOUT + refresh_render_paths(false, false); +#else refresh_render_paths(); +#endif // ENABLE_PREVIEW_LAYOUT log_memory_used("Refreshed G-code extrusion paths, "); } +#if !ENABLE_PREVIEW_LAYOUT void GCodeViewer::refresh_render_paths() { refresh_render_paths(false, false); } +#endif // !ENABLE_PREVIEW_LAYOUT void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) { @@ -800,7 +815,7 @@ void GCodeViewer::reset() m_paths_bounding_box = BoundingBoxf3(); m_max_bounding_box = BoundingBoxf3(); m_max_print_height = 0.0f; - m_tool_colors = std::vector(); + m_tool_colors = std::vector(); m_extruders_count = 0; m_extruder_ids = std::vector(); m_filament_diameters = std::vector(); @@ -811,12 +826,20 @@ void GCodeViewer::reset() m_layers_z_range = { 0, 0 }; m_roles = std::vector(); m_print_statistics.reset(); +#if ENABLE_PREVIEW_LAYER_TIME + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + m_layers_times[i] = std::vector(); + } +#endif // ENABLE_PREVIEW_LAYER_TIME m_custom_gcode_per_print_z = std::vector(); 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; +#if ENABLE_PREVIEW_LAYOUT + m_legend_resizer.reset(); +#endif // ENABLE_PREVIEW_LAYOUT } void GCodeViewer::render() @@ -926,7 +949,9 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); +#if !ENABLE_PREVIEW_LAYOUT flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); +#endif // !ENABLE_PREVIEW_LAYOUT return flags; } @@ -947,7 +972,9 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); +#if !ENABLE_PREVIEW_LAYOUT enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); +#endif // !ENABLE_PREVIEW_LAYOUT } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) @@ -978,7 +1005,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const return; // collect color information to generate materials - std::vector colors; + std::vector colors; for (const RenderPath& path : t_buffer.render_paths) { colors.push_back(path.color); } @@ -1000,10 +1027,10 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const 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) { + for (const ColorRGBA& color : colors) { fprintf(fp, "\nnewmtl material_%d\n", colors_count++); fprintf(fp, "Ka 1 1 1\n"); - fprintf(fp, "Kd %g %g %g\n", color[0], color[1], color[2]); + fprintf(fp, "Kd %g %g %g\n", color.r(), color.g(), color.b()); fprintf(fp, "Ks 0 0 0\n"); } @@ -1064,7 +1091,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const } size_t i = 0; - for (const Color& color : colors) { + for (const ColorRGBA& color : colors) { // save material triangles to file fprintf(fp, "\nusemtl material_%zu\n", i + 1); fprintf(fp, "# triangles material %zu\n", i + 1); @@ -2012,7 +2039,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) 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; + m_layers.get_ranges().back().last = move_id; // extruder ids m_extruder_ids.emplace_back(move.extruder_id); // roles @@ -2021,7 +2048,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) } 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; + m_layers.get_ranges().back().last = move_id; last_travel_s_id = move_id; } @@ -2126,7 +2153,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) for (GLVolume* volume : m_shells.volumes.volumes) { volume->zoom_to_volumes = false; - volume->color[3] = 0.25f; + volume->color.a(0.25f); volume->force_native_color = true; volume->set_render_color(); } @@ -2139,7 +2166,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool #endif // ENABLE_GCODE_VIEWER_STATISTICS auto extrusion_color = [this](const Path& path) { - Color color; + ColorRGBA color; switch (m_view_type) { case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } @@ -2148,17 +2175,37 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool 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; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: + case EViewType::LayerTimeLogarithmic: { + const Path::Sub_Path& sub_path = path.sub_paths.front(); + double z = static_cast(sub_path.first.position.z()); + const std::vector& zs = m_layers.get_zs(); + const std::vector& ranges = m_layers.get_ranges(); + size_t time_mode_id = static_cast(m_time_estimate_mode); + for (size_t i = 0; i < zs.size(); ++i) { + if (std::abs(zs[i] - z) < EPSILON) { + if (ranges[i].contains(sub_path.first.s_id)) { + color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i], + (m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic); + break; + } + } + } + break; + } +#endif // ENABLE_PREVIEW_LAYER_TIME case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { if (path.cp_color_id >= static_cast(m_tool_colors.size())) - color = { 0.5f, 0.5f, 0.5f, 1.0f }; + color = ColorRGBA::GRAY(); else color = m_tool_colors[path.cp_color_id]; break; } - default: { color = { 1.0f, 1.0f, 1.0f, 1.0f }; break; } + default: { color = ColorRGBA::WHITE(); break; } } return color; @@ -2172,7 +2219,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool 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 m_layers.get_range_at(min_id).first <= id && id <= m_layers.get_range_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); @@ -2197,8 +2244,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool 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; + const size_t min_s_id = m_layers.get_range_at(min_id).first; + const size_t max_s_id = m_layers.get_range_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); @@ -2231,14 +2278,14 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool 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) + if (id < m_layers.get_range_at(m_layers_z_range[0]).first || m_layers.get_range_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) + if (id < m_layers.get_range_at(m_layers_z_range[1]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) continue; top_layer_endpoints.first = std::min(top_layer_endpoints.first, id); @@ -2357,7 +2404,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) continue; - Color color; + ColorRGBA color; switch (path.type) { case EMoveType::Tool_change: @@ -2676,7 +2723,7 @@ void GCodeViewer::render_toolpaths() // 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(path.color.data()))); + shader.set_uniform(uniform_color, path.color); 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; @@ -2700,7 +2747,7 @@ void GCodeViewer::render_toolpaths() // 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(path.color.data()))); + shader.set_uniform(uniform_color, path.color); glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_lines_calls_count; @@ -2718,7 +2765,7 @@ void GCodeViewer::render_toolpaths() // 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(path.color.data()))); + shader.set_uniform(uniform_color, path.color); glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_triangles_calls_count; @@ -2980,7 +3027,11 @@ void GCodeViewer::render_legend(float& legend_height) const float max_height = 0.75f * static_cast(cnv_size.get_height()); const float child_height = 0.3333f * max_height; ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); +#if ENABLE_PREVIEW_LAYOUT + imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); +#else imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); +#endif // ENABLE_PREVIEW_LAYOUT enum class EItemType : unsigned char { @@ -2991,15 +3042,21 @@ void GCodeViewer::render_legend(float& legend_height) }; const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; +#if ENABLE_PREVIEW_LAYER_TIME + bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); +#else 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())); +#endif // ENABLE_PREVIEW_LAYER_TIME const float icon_size = ImGui::GetTextLineHeight(); const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, + auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const ColorRGBA& color, const std::string& label, bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, double used_filament_m = 0.0, double used_filament_g = 0.0, std::function callback = nullptr) { @@ -3012,21 +3069,21 @@ void GCodeViewer::render_legend(float& legend_height) default: case EItemType::Rect: { 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 })); + ImGuiWrapper::to_ImU32(color)); break; } case EItemType::Circle: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 16); break; } case EItemType::Hexagon: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 6); break; } case EItemType::Line: { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGuiWrapper::to_ImU32(color), 3.0f); break; } } @@ -3078,7 +3135,25 @@ void GCodeViewer::render_legend(float& legend_height) } else { imgui.text(label); +#if ENABLE_TRAVEL_TIME + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + const float width = std::max(1.0f, percent_bar_size * percent / max_percent); + 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 : ""); + } + else if (used_filament_m > 0.0) { +#else if (used_filament_m > 0.0) { +#endif // ENABLE_TRAVEL_TIME char buf[64]; ImGui::SameLine(offsets[0]); ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); @@ -3104,6 +3179,7 @@ void GCodeViewer::render_legend(float& legend_height) // single item use case append_range_item(0, range.min, decimals); else if (range.count == 2) { + // two items use case append_range_item(static_cast(Range_Colors.size()) - 1, range.max, decimals); append_range_item(0, range.min, decimals); } @@ -3115,6 +3191,39 @@ void GCodeViewer::render_legend(float& legend_height) } }; +#if ENABLE_PREVIEW_LAYER_TIME + auto append_time_range = [append_item](const Extrusions::Range& range, Extrusions::Range::EType type) { + auto append_range_item = [append_item](int i, float value) { + std::string str_value = get_time_dhms(value); + if (str_value == "0s") + str_value = "< 1s"; + append_item(EItemType::Rect, Range_Colors[i], str_value); + }; + + if (range.count == 1) + // single item use case + append_range_item(0, range.min); + else if (range.count == 2) { + // two items use case + append_range_item(static_cast(Range_Colors.size()) - 1, range.max); + append_range_item(0, range.min); + } + else { + float step_size = range.step_size(type); + for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { + float value = 0.0f; + switch (type) + { + default: + case Extrusions::Range::EType::Linear: { value = range.min + static_cast(i) * step_size; break; } + case Extrusions::Range::EType::Logarithmic: { value = ::exp(::log(range.min) + static_cast(i) * step_size); break; } + } + append_range_item(i, value); + } + } + }; +#endif // ENABLE_PREVIEW_LAYER_TIME + auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { size_t i = 0; for (; i < offsets.size(); i++) { @@ -3144,7 +3253,7 @@ void GCodeViewer::render_legend(float& legend_height) }; auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { - std::vector>> ret; + std::vector>> ret; ret.reserve(custom_gcode_per_print_z.size()); for (const auto& item : custom_gcode_per_print_z) { @@ -3163,8 +3272,11 @@ void GCodeViewer::render_legend(float& legend_height) 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 } }); + if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) { + ColorRGBA color; + decode_color(item.color, color); + ret.push_back({ color, { previous_z, current_z } }); + } } return ret; @@ -3211,7 +3323,7 @@ void GCodeViewer::render_legend(float& legend_height) std::vector percents; std::vector used_filaments_m; std::vector used_filaments_g; - float max_percent = 0.0f; + float max_time_percent = 0.0f; if (m_view_type == EViewType::FeatureType) { // calculate offsets to align time/percentage data @@ -3222,7 +3334,7 @@ void GCodeViewer::render_legend(float& legend_height) 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); + max_time_percent = std::max(max_time_percent, percent); auto [used_filament_m, used_filament_g] = used_filament_per_role(role); used_filaments_m.push_back(used_filament_m); used_filaments_g.push_back(used_filament_g); @@ -3282,6 +3394,47 @@ void GCodeViewer::render_legend(float& legend_height) offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); } +#if ENABLE_PREVIEW_LAYOUT + // selection section + bool view_type_changed = false; + int old_view_type = static_cast(get_view_type()); + int view_type = old_view_type; + + if (!m_legend_resizer.dirty) + ImGui::SetNextItemWidth(-1.0f); + + ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f }); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f }); + imgui.combo("", { _u8L("Feature type"), + _u8L("Height (mm)"), + _u8L("Width (mm)"), + _u8L("Speed (mm/s)"), + _u8L("Fan speed (%)"), + _u8L("Temperature (°C)"), + _u8L("Volumetric flow rate (mm³/s)"), +#if ENABLE_PREVIEW_LAYER_TIME + _u8L("Layer time (linear)"), + _u8L("Layer time (logarithmic)"), +#endif // ENABLE_PREVIEW_LAYER_TIME + _u8L("Tool"), + _u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest); + ImGui::PopStyleColor(2); + + if (old_view_type != view_type) { + set_view_type(static_cast(view_type)); + wxGetApp().plater()->set_keep_current_preview_type(true); + wxGetApp().plater()->refresh_print(); + view_type_changed = true; + } + + // extrusion paths section -> title + if (m_view_type == EViewType::FeatureType) + append_headers({ _u8L(""), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); + else if (m_view_type == EViewType::Tool) + append_headers({ _u8L(""), _u8L("Used filament"), _u8L(""), _u8L("") }, offsets); + else + ImGui::Separator(); +#else // extrusion paths section -> title switch (m_view_type) { @@ -3290,58 +3443,81 @@ void GCodeViewer::render_legend(float& legend_height) append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); break; } - case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.title(_u8L("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::Tool: - { + case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } + case EViewType::Width: { imgui.title(_u8L("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; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: { imgui.title(_u8L("Layer time (linear)")); break; } + case EViewType::LayerTimeLogarithmic: { imgui.title(_u8L("Layer time (logarithmic)")); break; } +#endif // ENABLE_PREVIEW_LAYER_TIME + case EViewType::Tool: { append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); break; } - case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } + case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } default: { break; } } +#endif // ENABLE_PREVIEW_LAYOUT +#if ENABLE_PREVIEW_LAYOUT + if (!view_type_changed) { +#endif // ENABLE_PREVIEW_LAYOUT // extrusion paths section -> items switch (m_view_type) { case EViewType::FeatureType: { +#if ENABLE_TRAVEL_TIME + max_time_percent = std::max(max_time_percent, time_mode.travel_time / time_mode.time); +#endif // ENABLE_TRAVEL_TIME + for (size_t i = 0; i < m_roles.size(); ++i) { ExtrusionRole role = m_roles[i]; if (role >= erCount) continue; const bool visible = is_visible(role); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { + visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths refresh_render_paths(false, false); wxGetApp().plater()->update_preview_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); +#if !ENABLE_PREVIEW_LAYOUT wxGetApp().plater()->update_preview_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT } ); } + +#if ENABLE_TRAVEL_TIME + if (m_buffers[buffer_id(EMoveType::Travel)].visible) + append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time(get_time_dhms(time_mode.travel_time)), + time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f); +#endif // ENABLE_TRAVEL_TIME + break; } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } - case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } - case EViewType::Tool: - { + case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } + case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Linear); break; } + case EViewType::LayerTimeLogarithmic: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Logarithmic); break; } +#endif // ENABLE_PREVIEW_LAYER_TIME + case EViewType::Tool: { // shows only extruders actually used size_t i = 0; for (unsigned char extruder_id : m_extruder_ids) { append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); - i++; + ++i; } break; } @@ -3353,17 +3529,16 @@ void GCodeViewer::render_legend(float& legend_height) total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); } - const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + const bool need_scrollable = static_cast(total_items) * icon_size + (static_cast(total_items) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; // add scrollable region, if needed if (need_scrollable) ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); if (m_extruders_count == 1) { // single extruder use case - const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode + if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); - } else { for (int i = items_cnt; i >= 0; --i) { // create label for color change item @@ -3382,11 +3557,11 @@ void GCodeViewer::render_legend(float& legend_height) else { // multi extruder use case // shows only extruders actually used for (unsigned char i : m_extruder_ids) { - const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode + if (items_cnt == 0) + // There are no color changes, but there are some pause print or custom Gcode append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); - } else { for (int j = items_cnt; j >= 0; --j) { // create label for color change item @@ -3415,6 +3590,9 @@ void GCodeViewer::render_legend(float& legend_height) } default: { break; } } +#if ENABLE_PREVIEW_LAYOUT + } +#endif // ENABLE_PREVIEW_LAYOUT // partial estimated printing time section if (m_view_type == EViewType::ColorPrint) { @@ -3432,10 +3610,10 @@ void GCodeViewer::render_legend(float& legend_height) }; EType type; int extruder_id; - Color color1; - Color color2; + ColorRGBA color1; + ColorRGBA color2; Times times; - std::pair used_filament {0.0f, 0.0f}; + std::pair used_filament{ 0.0f, 0.0f }; }; using PartialTimes = std::vector; @@ -3444,7 +3622,7 @@ void GCodeViewer::render_legend(float& legend_height) std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; int extruders_count = wxGetApp().extruders_edited_cnt(); - std::vector last_color(extruders_count); + std::vector last_color(extruders_count); for (int i = 0; i < extruders_count; ++i) { last_color[i] = m_tool_colors[i]; } @@ -3456,8 +3634,8 @@ void GCodeViewer::render_legend(float& legend_height) 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 }); + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second }); + items.push_back({ PartialTime::EType::Pause, it->extruder, ColorRGBA::BLACK(), ColorRGBA::BLACK(), time_rec.second }); custom_gcode_per_print_z.erase(it); } break; @@ -3465,14 +3643,16 @@ void GCodeViewer::render_legend(float& legend_height) 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); + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder - 1) }); + ColorRGBA color; + decode_color(it->color, color); + items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], color, time_rec.second }); + last_color[it->extruder - 1] = color; last_extruder_id = it->extruder; custom_gcode_per_print_z.erase(it); } else - items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) }); + items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id - 1) }); break; } @@ -3483,7 +3663,7 @@ void GCodeViewer::render_legend(float& legend_height) return items; }; - auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { + auto append_color_change = [&imgui](const ColorRGBA& color1, const ColorRGBA& color2, const std::array& offsets, const Times& times) { imgui.text(_u8L("Color change")); ImGui::SameLine(); @@ -3493,16 +3673,16 @@ void GCodeViewer::render_legend(float& legend_height) 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 })); + ImGuiWrapper::to_ImU32(color1)); pos.x += icon_size; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); + ImGuiWrapper::to_ImU32(color2)); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second - times.first))); }; - auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { + auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array& offsets, const Times& times, std::pair used_filament) { imgui.text(_u8L("Print")); ImGui::SameLine(); @@ -3512,7 +3692,7 @@ void GCodeViewer::render_legend(float& legend_height) 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 })); + ImGuiWrapper::to_ImU32(color)); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second))); @@ -3560,7 +3740,7 @@ void GCodeViewer::render_legend(float& legend_height) ImGui::Spacing(); append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); - const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + const bool need_scrollable = static_cast(partial_times.size()) * icon_size + (static_cast(partial_times.size()) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; if (need_scrollable) // add scrollable region ImGui::BeginChild("events", { -1.0f, child_height }, false); @@ -3590,6 +3770,7 @@ void GCodeViewer::render_legend(float& legend_height) } } +#if !ENABLE_PREVIEW_LAYOUT // travel paths section if (m_buffers[buffer_id(EMoveType::Travel)].visible) { switch (m_view_type) @@ -3670,6 +3851,7 @@ void GCodeViewer::render_legend(float& legend_height) add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); } +#endif // !ENABLE_PREVIEW_LAYOUT // settings section bool has_settings = false; @@ -3751,7 +3933,7 @@ void GCodeViewer::render_legend(float& legend_height) if (can_show_mode_button(m_time_estimate_mode)) { switch (m_time_estimate_mode) { - case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } + case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]"; break; } default: { assert(false); break; } } @@ -3782,6 +3964,10 @@ void GCodeViewer::render_legend(float& legend_height) if (can_show_mode_button(mode)) { if (imgui.button(label)) { m_time_estimate_mode = mode; +#if ENABLE_PREVIEW_LAYER_TIME + if (m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic) + refresh_render_paths(false, false); +#endif // ENABLE_PREVIEW_LAYER_TIME imgui.set_requires_extra_frame(); } } @@ -3789,18 +3975,216 @@ void GCodeViewer::render_legend(float& legend_height) switch (m_time_estimate_mode) { case PrintEstimatedStatistics::ETimeMode::Normal: { - show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); + show_mode_button(_u8L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); break; } case PrintEstimatedStatistics::ETimeMode::Stealth: { - show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); + show_mode_button(_u8L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); break; } default : { assert(false); break; } } } - legend_height = ImGui::GetCurrentWindow()->Size.y; +#if ENABLE_PREVIEW_LAYOUT + // toolbar section + auto toggle_button = [this, &imgui, icon_size](Preview::OptionType type, const std::string& name, + std::function draw_callback) { + auto is_flag_set = [](unsigned int flags, unsigned int flag) { + return (flags & (1 << flag)) != 0; + }; + + auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { + return active ? (flags | (1 << flag)) : (flags & ~(1 << flag)); + }; + + unsigned int flags = get_options_visibility_flags(); + unsigned int flag = static_cast(type); + bool active = is_flag_set(flags, flag); + + if (imgui.draw_radio_button(name, 1.5f * icon_size, active, draw_callback)) { + unsigned int new_flags = set_flag(flags, flag, !active); + set_options_visibility_from_flags(new_flags); + + const unsigned int diff_flags = flags ^ new_flags; + if (m_view_type == GCodeViewer::EViewType::Feedrate && is_flag_set(diff_flags, static_cast(Preview::OptionType::Travel))) + wxGetApp().plater()->refresh_print(); + else { + bool keep_first = m_sequential_view.current.first != m_sequential_view.global.first; + bool keep_last = m_sequential_view.current.last != m_sequential_view.global.last; + wxGetApp().plater()->get_current_canvas3D()->refresh_gcode_preview_render_paths(keep_first, keep_last); + } + wxGetApp().plater()->update_preview_moves_slider(); + } + + if (ImGui::IsItemHovered()) { + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(name); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + } + }; + +#if ENABLE_LEGEND_TOOLBAR_ICONS +// auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { +// const float margin = 3.0f; +// const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); +// window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); +// }; +// auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { +// const float margin = 3.0f; +// window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); +// }; + auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) { + ImGuiIO& io = ImGui::GetIO(); + const ImTextureID tex_id = io.Fonts->TexID; + const float tex_w = static_cast(io.Fonts->TexWidth); + const float tex_h = static_cast(io.Fonts->TexHeight); + const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id); + const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h }; + const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; + window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 1.0f })); + }; +#else + auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { + const float margin = 3.0f; + const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); + window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + }; + auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { + const float margin = 3.0f; + window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + }; +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::Spacing(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendTravel); +#else + toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + line_icon(window, pos, size, Travel_Colors[0]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendWipe); +#else + toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + line_icon(window, pos, size, Wipe_Color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendRetract); +#else + toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Retractions)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendDeretract); +#else + toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Unretractions)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendSeams); +#else + toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Seams)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendToolChanges); +#else + toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ToolChanges)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendColorChanges); +#else + toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ColorChanges)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendPausePrints); +#else + toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::PausePrints)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendCustomGCodes); +#else + toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendShells); +#else + toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [](ImGuiWindow& window, const ImVec2& pos, float size) { + const ImU32 color = ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 1.0f }); + const float margin = 3.0f; + const float proj = 0.25f * size; + window.DrawList->AddRect({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin - proj, pos.y + margin + proj }, color); + window.DrawList->AddLine({ pos.x + margin, pos.y + margin + proj }, { pos.x + margin + proj, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + margin + proj }, { pos.x + size - margin, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + size - margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); + window.DrawList->AddLine({ pos.x + margin + proj, pos.y + margin }, { pos.x + size - margin, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin, pos.y + margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendToolMarker); +#else + toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [](ImGuiWindow& window, const ImVec2& pos, float size) { + const ImU32 color = ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 0.8f }); + const float margin = 3.0f; + const ImVec2 p1(0.5f * (pos.x + pos.x + size), pos.y + size - margin); + const ImVec2 p2 = ImVec2(p1.x + 0.25f * size, p1.y - 0.25f * size); + const ImVec2 p3 = ImVec2(p1.x - 0.25f * size, p1.y - 0.25f * size); + window.DrawList->AddTriangleFilled(p1, p2, p3, color); + const float mid_x = 0.5f * (pos.x + pos.x + size); + window.DrawList->AddRectFilled({ mid_x - 0.09375f * size, p1.y - 0.25f * size }, { mid_x + 0.09375f * size, pos.y + margin }, color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + + bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth(); + if (m_legend_resizer.dirty || size_dirty != m_legend_resizer.dirty) { + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + m_legend_resizer.dirty = size_dirty; +#endif // ENABLE_PREVIEW_LAYOUT + + legend_height = ImGui::GetWindowHeight(); imgui.end(); ImGui::PopStyleVar(); @@ -3922,14 +4306,14 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) } } int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); - layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints); + layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_ranges(), Layers::Range); 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 +ColorRGBA GCodeViewer::option_color(EMoveType move_type) const { switch (move_type) { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index a67208f10b..f7adcc9eb3 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -22,7 +22,6 @@ namespace GUI { class GCodeViewer { using IBufferType = unsigned short; - using Color = std::array; using VertexBuffer = std::vector; using MultiVertexBuffer = std::vector; using IndexBuffer = std::vector; @@ -31,12 +30,12 @@ class GCodeViewer using InstanceIdBuffer = std::vector; using InstancesOffsets = std::vector; - static const std::vector Extrusion_Role_Colors; - static const std::vector Options_Colors; - static const std::vector Travel_Colors; - static const std::vector Range_Colors; - static const Color Wipe_Color; - static const Color Neutral_Color; + static const std::vector Extrusion_Role_Colors; + static const std::vector Options_Colors; + static const std::vector Travel_Colors; + static const std::vector Range_Colors; + static const ColorRGBA Wipe_Color; + static const ColorRGBA Neutral_Color; enum class EOptionsColors : unsigned char { @@ -121,7 +120,7 @@ class GCodeViewer // vbo id unsigned int vbo{ 0 }; // Color to apply to the instances - Color color; + ColorRGBA color; }; std::vector ranges; @@ -243,7 +242,7 @@ class GCodeViewer // Index of the parent tbuffer unsigned char tbuffer_id; // Render path property - Color color; + ColorRGBA color; // Index of the buffer in TBuffer::indices unsigned int ibuffer_id; // Render path content @@ -263,12 +262,10 @@ class GCodeViewer bool operator() (const RenderPath &l, const RenderPath &r) const { if (l.tbuffer_id < r.tbuffer_id) return true; - for (int i = 0; i < 3; ++i) { - if (l.color[i] < r.color[i]) - return true; - else if (l.color[i] > r.color[i]) - return false; - } + if (l.color < r.color) + return true; + else if (l.color > r.color) + return false; return l.ibuffer_id < r.ibuffer_id; } }; @@ -299,7 +296,7 @@ class GCodeViewer struct Model { GLModel model; - Color color; + ColorRGBA color; InstanceVBuffer instances; GLModel::InitializationData data; @@ -384,6 +381,14 @@ class GCodeViewer { struct Range { +#if ENABLE_PREVIEW_LAYER_TIME + enum class EType : unsigned char + { + Linear, + Logarithmic + }; +#endif // ENABLE_PREVIEW_LAYER_TIME + float min; float max; unsigned int count; @@ -398,8 +403,13 @@ class GCodeViewer } void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; } +#if ENABLE_PREVIEW_LAYER_TIME + float step_size(EType type = EType::Linear) const; + ColorRGBA get_color_at(float value, EType type = EType::Linear) const; +#else float step_size() const { return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); } - Color get_color_at(float value) const; + ColorRGBA get_color_at(float value) const; +#endif // ENABLE_PREVIEW_LAYER_TIME }; struct Ranges @@ -416,6 +426,10 @@ class GCodeViewer Range volumetric_rate; // Color mapping by extrusion temperature. Range temperature; +#if ENABLE_PREVIEW_LAYER_TIME + // Color mapping by layer time. + std::array(PrintEstimatedStatistics::ETimeMode::Count)> layer_time; +#endif // ENABLE_PREVIEW_LAYER_TIME void reset() { height.reset(); @@ -424,6 +438,11 @@ class GCodeViewer fan_speed.reset(); volumetric_rate.reset(); temperature.reset(); +#if ENABLE_PREVIEW_LAYER_TIME + for (auto& range : layer_time) { + range.reset(); + } +#endif // ENABLE_PREVIEW_LAYER_TIME } }; @@ -443,42 +462,43 @@ class GCodeViewer class Layers { public: - struct Endpoints + struct Range { size_t first{ 0 }; size_t last{ 0 }; - bool operator == (const Endpoints& other) const { return first == other.first && last == other.last; } - bool operator != (const Endpoints& other) const { return !operator==(other); } + bool operator == (const Range& other) const { return first == other.first && last == other.last; } + bool operator != (const Range& other) const { return !operator==(other); } + bool contains(size_t id) const { return first <= id && id <= last; } }; private: std::vector m_zs; - std::vector m_endpoints; + std::vector m_ranges; public: - void append(double z, Endpoints endpoints) { + void append(double z, const Range& range) { m_zs.emplace_back(z); - m_endpoints.emplace_back(endpoints); + m_ranges.emplace_back(range); } void reset() { m_zs = std::vector(); - m_endpoints = std::vector(); + m_ranges = std::vector(); } size_t size() const { return m_zs.size(); } bool empty() const { return m_zs.empty(); } const std::vector& get_zs() const { return m_zs; } - const std::vector& get_endpoints() const { return m_endpoints; } - std::vector& get_endpoints() { return m_endpoints; } + const std::vector& get_ranges() const { return m_ranges; } + std::vector& get_ranges() { return m_ranges; } double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } - Endpoints get_endpoints_at(unsigned int id) const { return (id < m_endpoints.size()) ? m_endpoints[id] : Endpoints(); } + Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); } bool operator != (const Layers& other) const { if (m_zs != other.m_zs) return true; - if (m_endpoints != other.m_endpoints) + if (m_ranges != other.m_ranges) return true; return false; } @@ -491,7 +511,7 @@ class GCodeViewer TBuffer* buffer{ nullptr }; unsigned int ibo{ 0 }; unsigned int vbo{ 0 }; - Color color; + ColorRGBA color; ~SequentialRangeCap(); bool is_renderable() const { return buffer != nullptr; } @@ -680,6 +700,10 @@ public: FanSpeed, Temperature, VolumetricRate, +#if ENABLE_PREVIEW_LAYER_TIME + LayerTimeLinear, + LayerTimeLogarithmic, +#endif // ENABLE_PREVIEW_LAYER_TIME Tool, ColorPrint, Count @@ -695,7 +719,7 @@ private: // bounding box of toolpaths + marker tools BoundingBoxf3 m_max_bounding_box; float m_max_print_height{ 0.0f }; - std::vector m_tool_colors; + std::vector m_tool_colors; Layers m_layers; std::array m_layers_z_range; std::vector m_roles; @@ -708,6 +732,14 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; +#if ENABLE_PREVIEW_LAYOUT + struct LegendResizer + { + bool dirty{ true }; + void reset() { dirty = true; } + }; + LegendResizer m_legend_resizer; +#endif // ENABLE_PREVIEW_LAYOUT PrintEstimatedStatistics m_print_statistics; PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; #if ENABLE_GCODE_VIEWER_STATISTICS @@ -716,6 +748,9 @@ private: std::array m_detected_point_sizes = { 0.0f, 0.0f }; GCodeProcessorResult::SettingsIds m_settings_ids; std::array m_sequential_range_caps; +#if ENABLE_PREVIEW_LAYER_TIME + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> m_layers_times; +#endif // ENABLE_PREVIEW_LAYER_TIME std::vector m_custom_gcode_per_print_z; @@ -731,7 +766,11 @@ public: void load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized); // recalculate ranges in dependence of what is visible and sets tool/print colors void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); +#if ENABLE_PREVIEW_LAYOUT + void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; +#else void refresh_render_paths(); +#endif // ENABLE_PREVIEW_LAYOUT void update_shells_color_by_extruder(const DynamicPrintConfig* config); void reset(); @@ -775,10 +814,16 @@ public: std::vector& get_custom_gcode_per_print_z() { return m_custom_gcode_per_print_z; } size_t get_extruders_count() { return m_extruders_count; } +#if ENABLE_PREVIEW_LAYOUT + void invalidate_legend() { m_legend_resizer.reset(); } +#endif // ENABLE_PREVIEW_LAYOUT + private: void load_toolpaths(const GCodeProcessorResult& gcode_result); void load_shells(const Print& print, bool initialized); +#if !ENABLE_PREVIEW_LAYOUT void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; +#endif // !ENABLE_PREVIEW_LAYOUT void render_toolpaths(); void render_shells(); void render_legend(float& legend_height); @@ -790,7 +835,7 @@ private: } bool is_visible(const Path& path) const { return is_visible(path.role); } void log_memory_used(const std::string& label, int64_t additional = 0) const; - Color option_color(EMoveType move_type) const; + ColorRGBA option_color(EMoveType move_type) const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1d305f7665..17977544c1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -72,11 +72,10 @@ static constexpr const float TRACKBALLSIZE = 0.8f; -static constexpr const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f }; -static constexpr const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; -static constexpr const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; -static constexpr const float ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; -//static constexpr const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; +static const Slic3r::ColorRGB DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f }; +static const Slic3r::ColorRGB DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f }; +static const Slic3r::ColorRGB ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f }; +static const Slic3r::ColorRGB ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f }; // Number of floats static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB @@ -849,8 +848,8 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons void GLCanvas3D::SequentialPrintClearance::render() { - std::array FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; - std::array NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; + const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; + const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) @@ -2115,12 +2114,21 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, co request_extra_frame(); } +#if ENABLE_PREVIEW_LAYOUT +void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) +{ + m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); + set_as_dirty(); + request_extra_frame(); +} +#else void GLCanvas3D::refresh_gcode_preview_render_paths() { m_gcode_viewer.refresh_render_paths(); set_as_dirty(); request_extra_frame(); } +#endif // ENABLE_PREVIEW_LAYOUT void GLCanvas3D::load_sla_preview() { @@ -2424,12 +2432,16 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'i': { _update_camera_zoom(1.0); break; } case 'K': case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; } - case 'L': - case 'l': { - if (!m_main_toolbar.is_enabled()) { + case 'L': + case 'l': { + if (!m_main_toolbar.is_enabled()) { +#if ENABLE_PREVIEW_LAYOUT + show_legend(!is_legend_shown()); +#else m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); m_dirty = true; wxGetApp().plater()->update_preview_bottom_toolbar(); +#endif // ENABLE_PREVIEW_LAYOUT } break; } @@ -3812,6 +3824,9 @@ void GLCanvas3D::set_cursor(ECursorType type) void GLCanvas3D::msw_rescale() { +#if ENABLE_PREVIEW_LAYOUT + m_gcode_viewer.invalidate_legend(); +#endif // ENABLE_PREVIEW_LAYOUT } void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() @@ -4041,8 +4056,11 @@ bool GLCanvas3D::_render_search_list(float pos_x) action_taken = true; else sidebar.jump_to_option(selected);*/ - if (selected != 9999) + if (selected != 9999) { + imgui->end(); // end imgui before the jump to option sidebar.jump_to_option(selected); + return true; + } action_taken = true; } @@ -4156,9 +4174,6 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const return ret; }; - static const std::array orange = { 0.923f, 0.504f, 0.264f, 1.0f }; - static const std::array gray = { 0.64f, 0.64f, 0.64f, 1.0f }; - GLVolumePtrs visible_volumes; for (GLVolume* vol : volumes.volumes) { @@ -4213,7 +4228,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const shader->set_uniform("emission_factor", 0.0f); for (GLVolume* vol : visible_volumes) { - shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : orange) : gray); + shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); // the volume may have been deactivated by an active gizmo bool is_active = vol->is_active; vol->is_active = true; @@ -4944,19 +4959,20 @@ void GLCanvas3D::_picking_pass() int volume_id = -1; int gizmo_id = -1; - GLubyte color[4] = { 0, 0, 0, 0 }; + std::array color = { 0, 0, 0, 0 }; const Size& cnv_size = get_canvas_size(); bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height(); if (inside) { - glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); + glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position.y() - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color.data())); if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) { // Only non-interpolated colors are valid, those have their lowest three bits zeroed. // we reserve color = (0,0,0) for occluders (as the printbed) // volumes' id are shifted by 1 // see: _render_volumes_for_picking() - volume_id = color[0] + (color[1] << 8) + (color[2] << 16) - 1; + unsigned int id = picking_encode(color[0], color[1], color[2]); + volume_id = id - 1; // gizmos' id are instead properly encoded by the color - gizmo_id = color[0] + (color[1] << 8) + (color[2] << 16); + gizmo_id = id; } } if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { @@ -5072,19 +5088,11 @@ void GLCanvas3D::_render_background() const glsafe(::glDisable(GL_DEPTH_TEST)); ::glBegin(GL_QUADS); - if (use_error_color) - ::glColor3fv(ERROR_BG_DARK_COLOR); - else - ::glColor3fv(DEFAULT_BG_DARK_COLOR); - + ::glColor3fv(use_error_color ? ERROR_BG_DARK_COLOR.data(): DEFAULT_BG_DARK_COLOR.data()); ::glVertex2f(-1.0f, -1.0f); ::glVertex2f(1.0f, -1.0f); - if (use_error_color) - ::glColor3fv(ERROR_BG_LIGHT_COLOR); - else - ::glColor3fv(DEFAULT_BG_LIGHT_COLOR); - + ::glColor3fv(use_error_color ? ERROR_BG_LIGHT_COLOR.data() : DEFAULT_BG_LIGHT_COLOR.data()); ::glVertex2f(1.0f, 1.0f); ::glVertex2f(-1.0f, 1.0f); glsafe(::glEnd()); @@ -5367,8 +5375,6 @@ void GLCanvas3D::_render_overlays() void GLCanvas3D::_render_volumes_for_picking() const { - static const GLfloat INV_255 = 1.0f / 255.0f; - // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); @@ -5383,13 +5389,9 @@ void GLCanvas3D::_render_volumes_for_picking() const // Object picking mode. Render the object with a color encoding the object index. // we reserve color = (0,0,0) for occluders (as the printbed) // so we shift volumes' id by 1 to get the proper color - unsigned int id = 1 + volume.second.first; - unsigned int r = (id & (0x000000FF << 0)) << 0; - unsigned int g = (id & (0x000000FF << 8)) >> 8; - unsigned int b = (id & (0x000000FF << 16)) >> 16; - unsigned int a = picking_checksum_alpha_channel(r, g, b); - glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255)); - volume.first->render(); + const unsigned int id = 1 + volume.second.first; + glsafe(::glColor4fv(picking_decode(id).data())); + volume.first->render(); } } @@ -5815,7 +5817,7 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) if (!print->has_skirt() && !print->has_brim()) return; - const std::array color = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish + const ColorRGBA color = ColorRGBA::GREENISH(); // number of skirt layers size_t total_layer_count = 0; @@ -5862,7 +5864,8 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) { - std::vector> tool_colors = _parse_colors(str_tool_colors); + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); struct Ctxt { @@ -5871,20 +5874,20 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool has_perimeters; bool has_infill; bool has_support; - const std::vector>* tool_colors; + const std::vector* tool_colors; bool is_single_material_print; int extruders_cnt; const std::vector* color_print_values; - static const std::array& color_perimeters() { static std::array color = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow - static const std::array& color_infill() { static std::array color = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish - static const std::array& color_support() { static std::array color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - static const std::array& color_pause_or_custom_code() { static std::array color = { 0.5f, 0.5f, 0.5f, 1.f }; return color; } // gray + static ColorRGBA color_perimeters() { return ColorRGBA::YELLOW(); } + static ColorRGBA color_infill() { return ColorRGBA::REDISH(); } + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } + static ColorRGBA color_pause_or_custom_code() { return ColorRGBA::GRAY(); } // For cloring by a tool, return a parsed color. bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; } - const std::array& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } // For coloring by a color_print(M600), return a parsed color. bool color_by_color_print() const { return color_print_values!=nullptr; } @@ -6024,7 +6027,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const std::array& color) { + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { // Allocate the volume before locking. GLVolume *volume = new GLVolume(color); volume->is_extrusion_path = true; @@ -6165,21 +6168,22 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con if (!print->is_step_done(psWipeTower)) return; - std::vector> tool_colors = _parse_colors(str_tool_colors); + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); struct Ctxt { const Print *print; - const std::vector>* tool_colors; + const std::vector* tool_colors; Vec2f wipe_tower_pos; float wipe_tower_angle; - static const std::array& color_support() { static std::array color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } // For cloring by a tool, return a parsed color. bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; } - const std::array& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } int volume_idx(int tool, int feature) const { return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; } @@ -6211,7 +6215,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); size_t grain_size = std::max(n_items / 128, size_t(1)); tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const std::array& color) { + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { auto *volume = new GLVolume(color); volume->is_extrusion_path = true; tbb::spin_mutex::scoped_lock lock; @@ -6330,7 +6334,7 @@ void GLCanvas3D::_load_sla_shells() return; auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, - const TriangleMesh& mesh, const std::array& color, bool outside_printer_detection_enabled) { + const TriangleMesh& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); #if ENABLE_SMOOTH_NORMALS @@ -6392,28 +6396,6 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) _set_warning_notification(warning, show); } -std::vector> GLCanvas3D::_parse_colors(const std::vector& colors) -{ - static const float INV_255 = 1.0f / 255.0f; - - std::vector> output(colors.size(), { 1.0f, 1.0f, 1.0f, 1.0f }); - for (size_t i = 0; i < colors.size(); ++i) { - const std::string& color = colors[i]; - 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; - - output[i][j] = float(digit1 * 16 + digit2) * INV_255; - } - } - } - return output; -} - void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) { enum ErrorType{ diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 605d75ddd2..ebffe46957 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -731,7 +731,11 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); void load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); +#if ENABLE_PREVIEW_LAYOUT + void refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last); +#else void refresh_gcode_preview_render_paths(); +#endif // ENABLE_PREVIEW_LAYOUT void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } void load_sla_preview(); @@ -824,6 +828,11 @@ public: bool are_labels_shown() const { return m_labels.is_shown(); } void show_labels(bool show) { m_labels.show(show); } +#if ENABLE_PREVIEW_LAYOUT + bool is_legend_shown() const { return m_gcode_viewer.is_legend_enabled(); } + void show_legend(bool show) { m_gcode_viewer.enable_legend(show); m_dirty = true; } +#endif // ENABLE_PREVIEW_LAYOUT + bool is_using_slope() const { return m_slope.is_used(); } void use_slope(bool use) { m_slope.use(use); } void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); } @@ -981,8 +990,6 @@ private: bool _deactivate_arrange_menu(); float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); } - - static std::vector> _parse_colors(const std::vector& colors); }; } // namespace GUI diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 422b654080..fa804efc2f 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -165,7 +165,7 @@ bool GLModel::init_from_file(const std::string& filename) return true; } -void GLModel::set_color(int entity_id, const std::array& color) +void GLModel::set_color(int entity_id, const ColorRGBA& color) { for (size_t i = 0; i < m_render_data.size(); ++i) { if (entity_id == -1 || static_cast(i) == entity_id) diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index d47c56fd93..62722b18cf 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -3,6 +3,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Color.hpp" #include #include @@ -33,7 +34,7 @@ namespace GUI { unsigned int vbo_id{ 0 }; unsigned int ibo_id{ 0 }; size_t indices_count{ 0 }; - std::array color{ 1.0f, 1.0f, 1.0f, 1.0f }; + ColorRGBA color; }; struct InitializationData @@ -44,7 +45,7 @@ namespace GUI { std::vector positions; std::vector normals; std::vector indices; - std::array color{ 1.0f, 1.0f, 1.0f, 1.0f }; + ColorRGBA color; }; std::vector entities; @@ -74,7 +75,7 @@ namespace GUI { bool init_from_file(const std::string& filename); // if entity_id == -1 set the color of all entities - void set_color(int entity_id, const std::array& color); + void set_color(int entity_id, const ColorRGBA& color); void reset(); void render() const; diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 9c1e936525..32b3d59601 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -4,6 +4,7 @@ #include "3DScene.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/format.hpp" +#include "libslic3r/Color.hpp" #include #include @@ -206,154 +207,114 @@ void GLShaderProgram::stop_using() const glsafe(::glUseProgram(0)); } -bool GLShaderProgram::set_uniform(const char* name, int value) const +void GLShaderProgram::set_uniform(int id, int value) const { - int id = get_uniform_location(name); - if (id >= 0) { - glsafe(::glUniform1i(id, static_cast(value))); - return true; - } - return false; + if (id >= 0) + glsafe(::glUniform1i(id, value)); } -bool GLShaderProgram::set_uniform(const char* name, bool value) const +void GLShaderProgram::set_uniform(int id, bool value) const { - return set_uniform(name, value ? 1 : 0); + set_uniform(id, value ? 1 : 0); } -bool GLShaderProgram::set_uniform(const char* name, float value) const +void GLShaderProgram::set_uniform(int id, float value) const { - int id = get_uniform_location(name); - if (id >= 0) { - glsafe(::glUniform1f(id, static_cast(value))); - return true; - } - return false; + if (id >= 0) + glsafe(::glUniform1f(id, value)); } -bool GLShaderProgram::set_uniform(const char* name, double value) const +void GLShaderProgram::set_uniform(int id, double value) const { - return set_uniform(name, static_cast(value)); + set_uniform(id, static_cast(value)); } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform2iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform3iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform4iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform2fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform4fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const float* value, size_t size) const +void GLShaderProgram::set_uniform(int id, const float* value, size_t size) const { - if (size == 1) - return set_uniform(name, value[0]); - else if (size < 5) { - int id = get_uniform_location(name); - if (id >= 0) { - if (size == 2) - glsafe(::glUniform2fv(id, 1, static_cast(value))); - else if (size == 3) - glsafe(::glUniform3fv(id, 1, static_cast(value))); - else - glsafe(::glUniform4fv(id, 1, static_cast(value))); - - return true; - } - } - return false; -} - -bool GLShaderProgram::set_uniform(const char* name, const Transform3f& value) const -{ - int id = get_uniform_location(name); if (id >= 0) { + if (size == 1) + set_uniform(id, value[0]); + else if (size == 2) + glsafe(::glUniform2fv(id, 1, static_cast(value))); + else if (size == 3) + glsafe(::glUniform3fv(id, 1, static_cast(value))); + else if (size == 4) + glsafe(::glUniform4fv(id, 1, static_cast(value))); + } +} + +void GLShaderProgram::set_uniform(int id, const Transform3f& value) const +{ + if (id >= 0) glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.matrix().data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Transform3d& value) const +void GLShaderProgram::set_uniform(int id, const Transform3d& value) const { - return set_uniform(name, value.cast()); + set_uniform(id, value.cast()); } -bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const +void GLShaderProgram::set_uniform(int id, const Matrix3f& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const +void GLShaderProgram::set_uniform(int id, const Vec3f& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const +void GLShaderProgram::set_uniform(int id, const Vec3d& value) const { - return set_uniform(name, static_cast(value.cast())); + set_uniform(id, static_cast(value.cast())); +} + +void GLShaderProgram::set_uniform(int id, const ColorRGB& value) const +{ + set_uniform(id, value.data(), 3); +} + +void GLShaderProgram::set_uniform(int id, const ColorRGBA& value) const +{ + set_uniform(id, value.data(), 4); } int GLShaderProgram::get_attrib_location(const char* name) const diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index d7b92000df..06a5c00e5c 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -9,6 +9,9 @@ namespace Slic3r { +class ColorRGB; +class ColorRGBA; + class GLShaderProgram { public: @@ -44,22 +47,43 @@ public: void start_using() const; void stop_using() const; - bool set_uniform(const char* name, int value) const; - bool set_uniform(const char* name, bool value) const; - bool set_uniform(const char* name, float value) const; - bool set_uniform(const char* name, double value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const float* value, size_t size) const; - bool set_uniform(const char* name, const Transform3f& value) const; - bool set_uniform(const char* name, const Transform3d& value) const; - bool set_uniform(const char* name, const Matrix3f& value) const; - bool set_uniform(const char* name, const Vec3f& value) const; - bool set_uniform(const char* name, const Vec3d& value) const; + void set_uniform(const char* name, int value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, bool value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, float value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, double value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const float* value, size_t size) const { set_uniform(get_uniform_location(name), value, size); } + void set_uniform(const char* name, const Transform3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Transform3d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Matrix3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Vec3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Vec3d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const ColorRGB& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const ColorRGBA& value) const { set_uniform(get_uniform_location(name), value); } + + void set_uniform(int id, int value) const; + void set_uniform(int id, bool value) const; + void set_uniform(int id, float value) const; + void set_uniform(int id, double value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const float* value, size_t size) const; + void set_uniform(int id, const Transform3f& value) const; + void set_uniform(int id, const Transform3d& value) const; + void set_uniform(int id, const Matrix3f& value) const; + void set_uniform(int id, const Vec3f& value) const; + void set_uniform(int id, const Vec3d& value) const; + void set_uniform(int id, const ColorRGB& value) const; + void set_uniform(int id, const ColorRGBA& value) const; // returns -1 if not found int get_attrib_location(const char* name) const; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 12a5fc4e2c..93cd9073a8 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -44,6 +44,7 @@ #include "libslic3r/Model.hpp" #include "libslic3r/I18N.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" @@ -1580,8 +1581,7 @@ void GUI_App::set_label_clr_modified(const wxColour& clr) if (m_color_label_modified == clr) return; m_color_label_modified = clr; - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); - std::string str = clr_str.ToStdString(); + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); app_config->set("label_clr_modified", str); app_config->save(); } @@ -1591,8 +1591,7 @@ void GUI_App::set_label_clr_sys(const wxColour& clr) if (m_color_label_sys == clr) return; m_color_label_sys = clr; - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); - std::string str = clr_str.ToStdString(); + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); app_config->set("label_clr_sys", str); app_config->save(); } @@ -1796,6 +1795,7 @@ void GUI_App::update_ui_from_settings() m_force_colors_update = false; mainframe->force_color_changed(); mainframe->diff_dialog.force_color_changed(); + mainframe->preferences_dialog->force_color_changed(); mainframe->printhost_queue_dlg()->force_color_changed(); #ifdef _MSW_DARK_MODE update_scrolls(mainframe); @@ -2316,40 +2316,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuPreferences: { - bool app_layout_changed = false; - { - // the dialog needs to be destroyed before the call to recreate_GUI() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - PreferencesDialog dlg(mainframe); - dlg.ShowModal(); - app_layout_changed = dlg.settings_layout_changed(); - if (dlg.seq_top_layer_only_changed()) - this->plater_->refresh_print(); - - if (dlg.recreate_GUI()) { - recreate_GUI(_L("Restart application") + dots); - return; - } -#ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 - } - if (app_layout_changed) { - // hide full main_sizer for mainFrame - mainframe->GetSizer()->Show(false); - mainframe->update_layout(); - mainframe->select_tab(size_t(0)); - } + open_preferences(); break; } case ConfigMenuLanguage: @@ -2397,36 +2364,34 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } -void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option) +void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) { - bool app_layout_changed = false; - { - // the dialog needs to be destroyed before the call to recreate_GUI() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - PreferencesDialog dlg(mainframe, open_on_tab, highlight_option); - dlg.ShowModal(); - app_layout_changed = dlg.settings_layout_changed(); + mainframe->preferences_dialog->show(highlight_option, tab_name); + + if (mainframe->preferences_dialog->recreate_GUI()) + recreate_GUI(_L("Restart application") + dots); + #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) #else - if (dlg.seq_top_layer_only_changed()) + if (mainframe->preferences_dialog->seq_top_layer_only_changed()) #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - this->plater_->refresh_print(); + this->plater_->refresh_print(); + #ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); } - if (app_layout_changed) { + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); + } +#endif // _WIN32 + + if (mainframe->preferences_dialog->settings_layout_changed()) { // hide full main_sizer for mainFrame mainframe->GetSizer()->Show(false); mainframe->update_layout(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 6e310e4910..d3009a0fa6 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -264,7 +264,7 @@ public: wxString current_language_code_safe() const; bool is_localized() const { return m_wxLocale->GetLocale() != "English"; } - void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string()); + void open_preferences(const std::string& highlight_option = std::string(), const std::string& tab_name = std::string()); virtual bool OnExceptionInMainLoop() override; // Calls wxLaunchDefaultBrowser if user confirms in dialog. diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 92223a767d..000199f93a 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -9,6 +9,8 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/I18N.hpp" + // To show a message box if GUI initialization ends up with an exception thrown. #include diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 156444a514..2cac5e3566 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -208,6 +208,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_layers_slider_sizer = create_layers_slider_sizer(); wxGetApp().UpdateDarkUI(m_bottom_toolbar_panel = new wxPanel(this)); +#if !ENABLE_PREVIEW_LAYOUT m_label_view_type = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("View")); #ifdef _WIN32 wxGetApp().UpdateDarkUI(m_choice_view_type = new BitmapComboBox(m_bottom_toolbar_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY)); @@ -221,6 +222,10 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_choice_view_type->Append(_L("Fan speed")); m_choice_view_type->Append(_L("Temperature")); m_choice_view_type->Append(_L("Volumetric flow rate")); +#if ENABLE_PREVIEW_LAYER_TIME + m_choice_view_type->Append(_L("Layer time (linear)")); + m_choice_view_type->Append(_L("Layer time (logarithmic)")); +#endif // ENABLE_PREVIEW_LAYER_TIME m_choice_view_type->Append(_L("Tool")); m_choice_view_type->Append(_L("Color Print")); m_choice_view_type->SetSelection(0); @@ -232,6 +237,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) #else long combo_style = wxCB_READONLY; #endif + m_combochecklist_features = new wxComboCtrl(); m_combochecklist_features->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, combo_style); std::string feature_items = GUI::into_u8( @@ -270,6 +276,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) get_option_type_string(OptionType::Legend) + "|1" ); Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); +#endif // !ENABLE_PREVIEW_LAYOUT m_left_sizer = new wxBoxSizer(wxVERTICAL); m_left_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); @@ -281,6 +288,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); +#if !ENABLE_PREVIEW_LAYOUT bottom_toolbar_sizer->AddSpacer(5); bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0); @@ -292,6 +300,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); bottom_toolbar_sizer->Hide(m_combochecklist_features); bottom_toolbar_sizer->AddSpacer(5); +#endif // !ENABLE_PREVIEW_LAYOUT bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0); m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer); @@ -353,7 +362,9 @@ void Preview::load_print(bool keep_z_range) else if (tech == ptSLA) load_print_as_sla(); +#if !ENABLE_PREVIEW_LAYOUT update_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT Layout(); } @@ -396,10 +407,12 @@ void Preview::refresh_print() void Preview::msw_rescale() { +#if !ENABLE_PREVIEW_LAYOUT #ifdef _WIN32 m_choice_view_type->Rescale(); m_choice_view_type->SetMinSize(m_choice_view_type->GetSize()); #endif +#endif // !ENABLE_PREVIEW_LAYOUT // rescale slider if (m_layers_slider != nullptr) m_layers_slider->msw_rescale(); if (m_moves_slider != nullptr) m_moves_slider->msw_rescale(); @@ -417,11 +430,13 @@ void Preview::sys_color_changed() wxWindowUpdateLocker noUpdates(this); wxGetApp().UpdateAllStaticTextDarkUI(m_bottom_toolbar_panel); +#if !ENABLE_PREVIEW_LAYOUT wxGetApp().UpdateDarkUI(m_choice_view_type); wxGetApp().UpdateDarkUI(m_combochecklist_features); wxGetApp().UpdateDarkUI(static_cast(m_combochecklist_features->GetPopupControl())); wxGetApp().UpdateDarkUI(m_combochecklist_options); wxGetApp().UpdateDarkUI(static_cast(m_combochecklist_options->GetPopupControl())); +#endif // !ENABLE_PREVIEW_LAYOUT #endif if (m_layers_slider != nullptr) @@ -445,19 +460,23 @@ void Preview::edit_layers_slider(wxKeyEvent& evt) void Preview::bind_event_handlers() { - this->Bind(wxEVT_SIZE, &Preview::on_size, this); + Bind(wxEVT_SIZE, &Preview::on_size, this); +#if !ENABLE_PREVIEW_LAYOUT m_choice_view_type->Bind(wxEVT_COMBOBOX, &Preview::on_choice_view_type, this); m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); m_combochecklist_options->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); +#endif // !ENABLE_PREVIEW_LAYOUT m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); } void Preview::unbind_event_handlers() { - this->Unbind(wxEVT_SIZE, &Preview::on_size, this); + Unbind(wxEVT_SIZE, &Preview::on_size, this); +#if !ENABLE_PREVIEW_LAYOUT m_choice_view_type->Unbind(wxEVT_COMBOBOX, &Preview::on_choice_view_type, this); m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); m_combochecklist_options->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); +#endif // !ENABLE_PREVIEW_LAYOUT m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); } @@ -478,6 +497,7 @@ void Preview::on_size(wxSizeEvent& evt) Refresh(); } +#if !ENABLE_PREVIEW_LAYOUT void Preview::on_choice_view_type(wxCommandEvent& evt) { int selection = m_choice_view_type->GetCurrentSelection(); @@ -544,6 +564,7 @@ void Preview::update_bottom_toolbar() } } } +#endif // !ENABLE_PREVIEW_LAYOUT wxBoxSizer* Preview::create_layers_slider_sizer() { @@ -953,10 +974,20 @@ void Preview::load_print_as_fff(bool keep_z_range) std::vector gcodes = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_canvas->get_custom_gcode_per_print_z(); +#if ENABLE_PREVIEW_LAYOUT + const GCodeViewer::EViewType choice = !gcodes.empty() ? + GCodeViewer::EViewType::ColorPrint : + (number_extruders > 1) ? GCodeViewer::EViewType::Tool : GCodeViewer::EViewType::FeatureType; + if (choice != gcode_view_type) { + m_canvas->set_gcode_view_preview_type(choice); + if (wxGetApp().is_gcode_viewer()) + m_keep_current_preview_type = true; + refresh_print(); + } +#else const wxString choice = !gcodes.empty() ? _L("Color Print") : (number_extruders > 1) ? _L("Tool") : _L("Feature type"); - int type = m_choice_view_type->FindString(choice); if (m_choice_view_type->GetSelection() != type) { if (0 <= type && type < static_cast(GCodeViewer::EViewType::Count)) { @@ -967,6 +998,7 @@ void Preview::load_print_as_fff(bool keep_z_range) refresh_print(); } } +#endif // ENABLE_PREVIEW_LAYOUT } if (zs.empty()) { @@ -1042,6 +1074,7 @@ void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event) m_canvas->render(); } +#if !ENABLE_PREVIEW_LAYOUT wxString Preview::get_option_type_string(OptionType type) const { switch (type) @@ -1061,6 +1094,7 @@ wxString Preview::get_option_type_string(OptionType type) const default: { return ""; } } } +#endif // !ENABLE_PREVIEW_LAYOUT } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 42246aa181..d9c32e8293 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -80,16 +80,18 @@ class Preview : public wxPanel wxBoxSizer* m_left_sizer { nullptr }; wxBoxSizer* m_layers_slider_sizer { nullptr }; wxPanel* m_bottom_toolbar_panel { nullptr }; +#if !ENABLE_PREVIEW_LAYOUT wxStaticText* m_label_view_type { nullptr }; #ifdef _WIN32 BitmapComboBox* m_choice_view_type { nullptr }; #else wxComboBox* m_choice_view_type { nullptr }; #endif - wxStaticText* m_label_show { nullptr }; + wxStaticText* m_label_show{ nullptr }; wxComboCtrl* m_combochecklist_features { nullptr }; size_t m_combochecklist_features_pos { 0 }; wxComboCtrl* m_combochecklist_options { nullptr }; +#endif // !ENABLE_PREVIEW_LAYOUT DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; @@ -126,7 +128,9 @@ public: CustomGCodes, Shells, ToolMarker, +#if !ENABLE_PREVIEW_LAYOUT Legend +#endif // !ENABLE_PREVIEW_LAYOUT }; Preview(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, @@ -154,12 +158,18 @@ public: bool is_loaded() const { return m_loaded; } +#if !ENABLE_PREVIEW_LAYOUT void update_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT void update_moves_slider(); void enable_moves_slider(bool enable); void move_moves_slider(wxKeyEvent& evt); void hide_layers_slider(); +#if ENABLE_PREVIEW_LAYOUT + void set_keep_current_preview_type(bool value) { m_keep_current_preview_type = value; } +#endif // ENABLE_PREVIEW_LAYOUT + private: bool init(wxWindow* parent, Bed3D& bed, Model* model); @@ -167,9 +177,11 @@ private: void unbind_event_handlers(); void on_size(wxSizeEvent& evt); +#if !ENABLE_PREVIEW_LAYOUT void on_choice_view_type(wxCommandEvent& evt); void on_combochecklist_features(wxCommandEvent& evt); void on_combochecklist_options(wxCommandEvent& evt); +#endif // !ENABLE_PREVIEW_LAYOUT // Create/Update/Reset double slider on 3dPreview wxBoxSizer* create_layers_slider_sizer(); @@ -186,7 +198,9 @@ private: void on_layers_slider_scroll_changed(wxCommandEvent& event); void on_moves_slider_scroll_changed(wxCommandEvent& event); +#if !ENABLE_PREVIEW_LAYOUT wxString get_option_type_string(OptionType type) const; +#endif // !ENABLE_PREVIEW_LAYOUT }; } // namespace GUI diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index a5ba218b52..d7d3529fee 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -406,14 +406,6 @@ public: std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics); -inline int 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; -} - class TaskTimer { std::chrono::milliseconds start_timer; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 393be1a4e8..21b5c13e62 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -25,15 +25,9 @@ GLGizmoBase::Grabber::Grabber() void GLGizmoBase::Grabber::render(bool hover, float size) const { - std::array render_color; - if (hover) { - render_color[0] = (1.0f - color[0]); - render_color[1] = (1.0f - color[1]); - render_color[2] = (1.0f - color[2]); - render_color[3] = color[3]; - } - else - render_color = color; + ColorRGBA render_color = color; + if (hover) + render_color = complementary(render_color); render(size, render_color, false); } @@ -48,15 +42,14 @@ float GLGizmoBase::Grabber::get_dragging_half_size(float size) const return get_half_size(size) * DraggingScaleFactor; } -void GLGizmoBase::Grabber::render(float size, const std::array& render_color, bool picking) const +void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color, bool picking) const { - if (! cube_initialized) { + if (!cube.is_initialized()) { // This cannot be done in constructor, OpenGL is not yet // initialized at that point (on Linux at least). indexed_triangle_set mesh = its_make_cube(1., 1., 1.); its_translate(mesh, Vec3f(-0.5, -0.5, -0.5)); const_cast(cube).init_from(mesh, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); - const_cast(cube_initialized) = true; } float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); @@ -90,25 +83,16 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u m_base_color = DEFAULT_BASE_COLOR; m_drag_color = DEFAULT_DRAG_COLOR; m_highlight_color = DEFAULT_HIGHLIGHT_COLOR; - m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24)); - m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.)); - m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.)); } void GLGizmoBase::set_hover_id(int id) { - if (m_grabbers.empty() || (id < (int)m_grabbers.size())) - { + if (m_grabbers.empty() || id < (int)m_grabbers.size()) { m_hover_id = id; on_set_hover_id(); } } -void GLGizmoBase::set_highlight_color(const std::array& color) -{ - m_highlight_color = color; -} - void GLGizmoBase::enable_grabber(unsigned int id) { if (id < m_grabbers.size()) @@ -162,22 +146,13 @@ bool GLGizmoBase::update_items_state() return res; }; -std::array GLGizmoBase::picking_color_component(unsigned int id) const +ColorRGBA GLGizmoBase::picking_color_component(unsigned int id) const { - static const float INV_255 = 1.0f / 255.0f; - id = BASE_ID - id; - if (m_group_id > -1) id -= m_group_id; - // color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass() - return std::array { - float((id >> 0) & 0xff) * INV_255, // red - float((id >> 8) & 0xff) * INV_255, // green - float((id >> 16) & 0xff) * INV_255, // blue - float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff))* INV_255 // checksum for validating against unwanted alpha blending and multi sampling - }; + return picking_decode(id); } void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const @@ -205,8 +180,7 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) { if (m_grabbers[i].enabled) { - std::array color = picking_color_component(i); - m_grabbers[i].color = color; + m_grabbers[i].color = picking_color_component(i); m_grabbers[i].render_for_picking(mean_size); } } @@ -244,22 +218,5 @@ std::string GLGizmoBase::get_name(bool include_shortcut) const } - -// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components -// were not interpolated by alpha blending or multi sampling. -unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) -{ - // 8 bit hash for the color - unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; - // Increase enthropy by a bit reversal - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - // Flip every second bit to increase the enthropy even more. - b ^= 0x55; - return b; -} - - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 66ca4fca27..ca7e3f2f7d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -2,6 +2,7 @@ #define slic3r_GLGizmoBase_hpp_ #include "libslic3r/Point.hpp" +#include "libslic3r/Color.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GLModel.hpp" @@ -18,15 +19,11 @@ class ModelObject; namespace GUI { -static const std::array DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f }; -static const std::array DEFAULT_DRAG_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; -static const std::array DEFAULT_HIGHLIGHT_COLOR = { 1.0f, 0.38f, 0.0f, 1.0f }; -static const std::array, 3> AXES_COLOR = {{ - { 0.75f, 0.0f, 0.0f, 1.0f }, - { 0.0f, 0.75f, 0.0f, 1.0f }, - { 0.0f, 0.0f, 0.75f, 1.0f } - }}; -static const std::array CONSTRAINED_COLOR = { 0.5f, 0.5f, 0.5f, 1.0f }; +static const ColorRGBA DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f }; +static const ColorRGBA DEFAULT_DRAG_COLOR = ColorRGBA::WHITE(); +static const ColorRGBA DEFAULT_HIGHLIGHT_COLOR = ColorRGBA::ORANGE(); +static const std::array AXES_COLOR = {{ ColorRGBA::X(), ColorRGBA::Y(), ColorRGBA::Z() }}; +static const ColorRGBA CONSTRAINED_COLOR = ColorRGBA::GRAY(); class ImGuiWrapper; class GLCanvas3D; @@ -50,7 +47,7 @@ protected: Vec3d center; Vec3d angles; - std::array color; + ColorRGBA color; bool enabled; bool dragging; @@ -63,10 +60,9 @@ protected: float get_dragging_half_size(float size) const; private: - void render(float size, const std::array& render_color, bool picking) const; + void render(float size, const ColorRGBA& render_color, bool picking) const; GLModel cube; - bool cube_initialized = false; }; public: @@ -97,17 +93,14 @@ protected: unsigned int m_sprite_id; int m_hover_id; bool m_dragging; - std::array m_base_color; - std::array m_drag_color; - std::array m_highlight_color; + ColorRGBA m_base_color; + ColorRGBA m_drag_color; + ColorRGBA m_highlight_color; mutable std::vector m_grabbers; ImGuiWrapper* m_imgui; bool m_first_input_window_render; mutable std::string m_tooltip; CommonGizmosDataPool* m_c; - GLModel m_cone; - GLModel m_cylinder; - GLModel m_sphere; public: GLGizmoBase(GLCanvas3D& parent, @@ -146,7 +139,7 @@ public: int get_hover_id() const { return m_hover_id; } void set_hover_id(int id); - void set_highlight_color(const std::array& color); + void set_highlight_color(const ColorRGBA& color) { m_highlight_color = color; } void enable_grabber(unsigned int id); void disable_grabber(unsigned int id); @@ -188,7 +181,8 @@ protected: // Returns the picking color for the given id, based on the BASE_ID constant // No check is made for clashing with other picking color (i.e. GLVolumes) - std::array picking_color_component(unsigned int id) const; + ColorRGBA picking_color_component(unsigned int id) const; + void render_grabbers(const BoundingBoxf3& box) const; void render_grabbers(float size) const; void render_grabbers_for_picking(const BoundingBoxf3& box) const; @@ -203,10 +197,6 @@ private: bool m_dirty; }; -// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components -// were not interpolated by alpha blending or multi sampling. -extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index b884d154dc..90e383e87d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -23,7 +23,8 @@ namespace GUI { const double GLGizmoCut::Offset = 10.0; const double GLGizmoCut::Margin = 20.0; -const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 }; +static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); +static const ColorRGBA PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5f }; GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -103,7 +104,7 @@ void GLGizmoCut::on_render() // Draw the cutting plane ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glColor4fv(PLANE_COLOR.data()); ::glVertex3f(min_x, min_y, plane_center.z()); ::glVertex3f(max_x, min_y, plane_center.z()); ::glVertex3f(max_x, max_y, plane_center.z()); @@ -134,7 +135,7 @@ void GLGizmoCut::on_render() shader->start_using(); shader->set_uniform("emission_factor", 0.1f); - m_grabbers[0].color = GrabberColor; + m_grabbers[0].color = GRABBER_COLOR; m_grabbers[0].render(m_hover_id == 0, (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0)); shader->stop_using(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 4007f89d47..44291a4094 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -13,7 +13,6 @@ class GLGizmoCut : public GLGizmoBase { static const double Offset; static const double Margin; - static const std::array GrabberColor; double m_cut_z{ 0.0 }; double m_max_z{ 0.0 }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 4413a4e429..fd32c68fc1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -13,6 +13,8 @@ namespace Slic3r { namespace GUI { +static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; +static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -74,11 +76,7 @@ void GLGizmoFlatten::on_render() if (this->is_plane_update_necessary()) update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { - if (i == m_hover_id) - glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.75f)); - else - glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.5f)); - + glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data())); if (m_planes[i].vbo.has_VBOs()) m_planes[i].vbo.render(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index a66bc4d0ee..ad1e7bcbe5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -20,7 +20,6 @@ namespace GUI { GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { - m_vbo_cylinder.init_from(its_make_cylinder(1., 1.)); } @@ -63,6 +62,9 @@ void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&) void GLGizmoHollow::on_render() { + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0)); + const Selection& selection = m_parent.get_selection(); const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); @@ -114,7 +116,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); glsafe(::glMultMatrixd(instance_matrix.data())); - std::array render_color; + ColorRGBA render_color; const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; size_t cache_size = drain_holes.size(); @@ -126,33 +128,25 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons continue; // First decide about the color of the point. - if (picking) { - std::array color = picking_color_component(i); - render_color = color; - } + if (picking) + render_color = picking_color_component(i); else { - if (size_t(m_hover_id) == i) { - render_color = {0.f, 1.f, 1.f, 1.f}; - } + if (size_t(m_hover_id) == i) + render_color = {0.0f, 1.0f, 1.0f, 1.0f}; else if (m_c->hollowed_mesh() && i < m_c->hollowed_mesh()->get_drainholes().size() && m_c->hollowed_mesh()->get_drainholes()[i].failed) { - render_color = {1.f, 0.f, 0.f, .5f}; - } - else { // neigher hover nor picking - - render_color[0] = point_selected ? 1.0f : 1.f; - render_color[1] = point_selected ? 0.3f : 1.f; - render_color[2] = point_selected ? 0.3f : 1.f; - render_color[3] = 0.5f; + render_color = {1.0f, 0.0f, 0.0f, 0.5f}; } + else // neither hover nor picking + render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); } - const_cast(&m_vbo_cylinder)->set_color(-1, render_color); + const_cast(&m_cylinder)->set_color(-1, render_color); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); + glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); if (vol->is_left_handed()) @@ -166,7 +160,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons glsafe(::glPushMatrix()); glsafe(::glTranslated(0., 0., -drain_hole.height)); glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - m_vbo_cylinder.render(); + m_cylinder.render(); glsafe(::glPopMatrix()); if (vol->is_left_handed()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 2cf08de2a0..bc144c2973 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -48,7 +48,8 @@ private: ObjectID m_old_mo_id = -1; - GLModel m_vbo_cylinder; + GLModel m_cylinder; + float m_new_hole_radius = 2.f; // Size of a new hole. float m_new_hole_height = 6.f; mutable std::vector m_selected; // which holes are currently selected diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 69d5ff9ed0..f25a650200 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -56,18 +56,12 @@ bool GLGizmoMmuSegmentation::on_is_activable() const return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1; } -static std::vector> get_extruders_colors() +static std::vector get_extruders_colors() { - unsigned char rgb_color[3] = {}; - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - std::vector> colors_out(colors.size()); - for (const std::string &color : colors) { - Slic3r::GUI::BitmapCache::parse_color(color, rgb_color); - size_t color_idx = &color - &colors.front(); - colors_out[color_idx] = {float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f}; - } - - return colors_out; + std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + std::vector ret; + decode_colors(colors, ret); + return ret; } static std::vector get_extruders_names() @@ -212,18 +206,14 @@ void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const } } -static void render_extruders_combo(const std::string &label, - const std::vector &extruders, - const std::vector> &extruders_colors, - size_t &selection_idx) +static void render_extruders_combo(const std::string& label, + const std::vector& extruders, + const std::vector& extruders_colors, + size_t& selection_idx) { assert(!extruders_colors.empty()); assert(extruders_colors.size() == extruders_colors.size()); - auto convert_to_imu32 = [](const std::array &color) -> ImU32 { - return IM_COL32(uint8_t(color[0] * 255.f), uint8_t(color[1] * 255.f), uint8_t(color[2] * 255.f), uint8_t(color[3] * 255.f)); - }; - size_t selection_out = selection_idx; // It is necessary to use BeginGroup(). Otherwise, when using SameLine() is called, then other items will be drawn inside the combobox. ImGui::BeginGroup(); @@ -239,7 +229,7 @@ static void render_extruders_combo(const std::string &labe ImGui::SameLine(); ImGuiStyle &style = ImGui::GetStyle(); float height = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), convert_to_imu32(extruders_colors[extruder_idx])); + ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), ImGuiWrapper::to_ImU32(extruders_colors[extruder_idx])); ImGui::GetWindowDrawList()->AddRect(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), IM_COL32_BLACK); ImGui::SetCursorScreenPos(ImVec2(start_position.x + height + height / 2 + style.FramePadding.x, start_position.y)); @@ -257,7 +247,7 @@ static void render_extruders_combo(const std::string &labe ImVec2 p = ImGui::GetCursorScreenPos(); float height = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), convert_to_imu32(extruders_colors[selection_idx])); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), ImGuiWrapper::to_ImU32(extruders_colors[selection_idx])); ImGui::GetWindowDrawList()->AddRect(p, ImVec2(p.x + height + height / 2, p.y + height), IM_COL32_BLACK); ImGui::SetCursorScreenPos(ImVec2(p.x + height + height / 2 + style.FramePadding.x, p.y)); @@ -339,10 +329,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott render_extruders_combo("##first_color_combo", m_original_extruders_names, m_original_extruders_colors, m_first_selected_extruder_idx); ImGui::SameLine(); - const std::array &select_first_color = m_modified_extruders_colors[m_first_selected_extruder_idx]; - ImVec4 first_color = ImVec4(select_first_color[0], select_first_color[1], select_first_color[2], select_first_color[3]); - if(ImGui::ColorEdit4("First color##color_picker", (float*)&first_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) - m_modified_extruders_colors[m_first_selected_extruder_idx] = {first_color.x, first_color.y, first_color.z, first_color.w}; + const ColorRGBA& select_first_color = m_modified_extruders_colors[m_first_selected_extruder_idx]; + ImVec4 first_color = ImGuiWrapper::to_ImVec4(select_first_color); + if (ImGui::ColorEdit4("First color##color_picker", (float*)&first_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) + m_modified_extruders_colors[m_first_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(first_color); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("second_color")); @@ -351,10 +341,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott render_extruders_combo("##second_color_combo", m_original_extruders_names, m_original_extruders_colors, m_second_selected_extruder_idx); ImGui::SameLine(); - const std::array &select_second_color = m_modified_extruders_colors[m_second_selected_extruder_idx]; - ImVec4 second_color = ImVec4(select_second_color[0], select_second_color[1], select_second_color[2], select_second_color[3]); - if(ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) - m_modified_extruders_colors[m_second_selected_extruder_idx] = {second_color.x, second_color.y, second_color.z, second_color.w}; + const ColorRGBA& select_second_color = m_modified_extruders_colors[m_second_selected_extruder_idx]; + ImVec4 second_color = ImGuiWrapper::to_ImVec4(select_second_color); + if (ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) + m_modified_extruders_colors[m_second_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(second_color); const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -565,16 +555,18 @@ PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const return PainterGizmoType::MMU_SEGMENTATION; } -std::array GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const +ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const { - const std::array &color = m_modified_extruders_colors[m_first_selected_extruder_idx]; - return {color[0], color[1], color[2], 0.25f}; + ColorRGBA color = m_modified_extruders_colors[m_first_selected_extruder_idx]; + color.a(0.25f); + return color; } -std::array GLGizmoMmuSegmentation::get_cursor_sphere_right_button_color() const +ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_right_button_color() const { - const std::array &color = m_modified_extruders_colors[m_second_selected_extruder_idx]; - return {color[0], color[1], color[2], 0.25f}; + ColorRGBA color = m_modified_extruders_colors[m_second_selected_extruder_idx]; + color.a(0.25f); + return color; } void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 577db8ed5a..70bb5241df 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -62,7 +62,7 @@ public: class TriangleSelectorMmGui : public TriangleSelectorGUI { public: // Plus 1 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the indices above colors.size() are allocated for seed fill. - explicit TriangleSelectorMmGui(const TriangleMesh &mesh, const std::vector> &colors, const std::array &default_volume_color) + TriangleSelectorMmGui(const TriangleMesh& mesh, const std::vector& colors, const ColorRGBA& default_volume_color) : TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(2 * (colors.size() + 1)) {} ~TriangleSelectorMmGui() override = default; @@ -73,8 +73,8 @@ public: private: void update_render_data(); - const std::vector> &m_colors; - const std::array m_default_volume_color; + const std::vector& m_colors; + const ColorRGBA m_default_volume_color; GLMmSegmentationGizmo3DScene m_gizmo_scene; }; @@ -100,8 +100,8 @@ public: const float get_cursor_radius_min() const override { return CursorRadiusMin; } protected: - std::array get_cursor_sphere_left_button_color() const override; - std::array get_cursor_sphere_right_button_color() const override; + ColorRGBA get_cursor_sphere_left_button_color() const override; + ColorRGBA get_cursor_sphere_right_button_color() const override; EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType(m_first_selected_extruder_idx + 1); } EnforcerBlockerType get_right_button_state_type() const override { return EnforcerBlockerType(m_second_selected_extruder_idx + 1); } @@ -121,8 +121,8 @@ protected: size_t m_first_selected_extruder_idx = 0; size_t m_second_selected_extruder_idx = 1; std::vector m_original_extruders_names; - std::vector> m_original_extruders_colors; - std::vector> m_modified_extruders_colors; + std::vector m_original_extruders_colors; + std::vector m_modified_extruders_colors; std::vector m_original_volumes_extruder_idxs; static const constexpr float CursorRadiusMin = 0.1f; // cannot be zero diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index e73a85647e..ff6a05847c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -20,7 +20,6 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filenam , m_starting_box_center(Vec3d::Zero()) , m_starting_box_bottom_center(Vec3d::Zero()) { - m_vbo_cone.init_from(its_make_cone(1., 1., 2*PI/36)); } std::string GLGizmoMove3D::get_tooltip() const @@ -89,6 +88,9 @@ void GLGizmoMove3D::on_update(const UpdateData& data) void GLGizmoMove3D::on_render() { + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 18.0)); + const Selection& selection = m_parent.get_selection(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); @@ -193,19 +195,15 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size(mean_size) : (double)m_grabbers[axis].get_half_size(mean_size); - std::array color = m_grabbers[axis].color; - if (!picking && m_hover_id != -1) { - color[0] = 1.0f - color[0]; - color[1] = 1.0f - color[1]; - color[2] = 1.0f - color[2]; - color[3] = color[3]; - } + ColorRGBA color = m_grabbers[axis].color; + if (!picking && m_hover_id != -1) + color = complementary(color); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; - const_cast(&m_vbo_cone)->set_color(-1, color); + const_cast(&m_cone)->set_color(-1, color); if (!picking) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); @@ -220,7 +218,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box glsafe(::glTranslated(0.0, 0.0, 2.0 * size)); glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size)); - m_vbo_cone.render(); + m_cone.render(); glsafe(::glPopMatrix()); if (! picking) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index baa2df7391..2d331bfb5c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -19,7 +19,7 @@ class GLGizmoMove3D : public GLGizmoBase Vec3d m_starting_box_center; Vec3d m_starting_box_bottom_center; - GLModel m_vbo_cone; + GLModel m_cone; public: GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 5bcc888ed8..1f654dcc04 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -18,13 +18,17 @@ namespace Slic3r::GUI { + std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { - // Make sphere and save it into a vertex buffer. - m_vbo_sphere.load_its_flat_shading(its_make_sphere(1., (2*M_PI)/24.)); - m_vbo_sphere.finalize_geometry(true); +} + +GLGizmoPainterBase::~GLGizmoPainterBase() +{ + if (s_sphere != nullptr && s_sphere->has_VBOs()) + s_sphere->release_geometry(); } void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) @@ -184,6 +188,12 @@ void GLGizmoPainterBase::render_cursor_circle() const void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const { + if (s_sphere == nullptr) { + s_sphere = std::make_shared(); + s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0)); + s_sphere->finalize_geometry(true); + } + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); @@ -197,14 +207,15 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const if (is_left_handed) glFrontFace(GL_CW); - std::array render_color = {0.f, 0.f, 0.f, 0.25f}; + ColorRGBA render_color = { 0.0f, 0.0f, 0.0f, 0.25f }; if (m_button_down == Button::Left) render_color = this->get_cursor_sphere_left_button_color(); else if (m_button_down == Button::Right) render_color = this->get_cursor_sphere_right_button_color(); glsafe(::glColor4fv(render_color.data())); - m_vbo_sphere.render(); + assert(s_sphere != nullptr); + s_sphere->render(); if (is_left_handed) glFrontFace(GL_CCW); @@ -698,15 +709,15 @@ TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed}); } -std::array TriangleSelectorGUI::get_seed_fill_color(const std::array &base_color) +ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color) { - return {base_color[0] * 0.75f, base_color[1] * 0.75f, base_color[2] * 0.75f, 1.f}; + return saturate(base_color, 0.75f); } void TriangleSelectorGUI::render(ImGuiWrapper* imgui) { - static constexpr std::array enforcers_color{0.47f, 0.47f, 1.f, 1.f}; - static constexpr std::array blockers_color{1.f, 0.44f, 0.44f, 1.f}; + static const ColorRGBA enforcers_color = { 0.47f, 0.47f, 1.0f, 1.0f }; + static const ColorRGBA blockers_color = { 1.0f, 0.44f, 0.44f, 1.0f }; if (m_update_render_data) { update_render_data(); @@ -730,7 +741,7 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) for (auto &iva : m_iva_seed_fills) if (iva.has_VBOs()) { size_t color_idx = &iva - &m_iva_seed_fills.front(); - const std::array &color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : color_idx == 2 ? blockers_color : GLVolume::NEUTRAL_COLOR); shader->set_uniform("uniform_color", color); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 97ac8e4e98..53c0f31b11 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -12,6 +12,7 @@ #include #include +#include namespace Slic3r::GUI { @@ -85,7 +86,7 @@ public: protected: bool m_update_render_data = false; - static std::array get_seed_fill_color(const std::array &base_color); + static ColorRGBA get_seed_fill_color(const ColorRGBA& base_color); private: void update_render_data(); @@ -112,7 +113,7 @@ private: void on_render_for_picking() override {} public: GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - ~GLGizmoPainterBase() override = default; + virtual ~GLGizmoPainterBase() override; virtual void set_painter_gizmo_data(const Selection& selection); virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); @@ -134,8 +135,8 @@ protected: virtual void update_model_object() const = 0; virtual void update_from_model_object() = 0; - virtual std::array get_cursor_sphere_left_button_color() const { return {0.f, 0.f, 1.f, 0.25f}; } - virtual std::array get_cursor_sphere_right_button_color() const { return {1.f, 0.f, 0.f, 0.25f}; } + virtual ColorRGBA get_cursor_sphere_left_button_color() const { return { 0.0f, 0.0f, 1.0f, 0.25f }; } + virtual ColorRGBA get_cursor_sphere_right_button_color() const { return { 1.0f, 0.0f, 0.0f, 0.25f }; } virtual EnforcerBlockerType get_left_button_state_type() const { return EnforcerBlockerType::ENFORCER; } virtual EnforcerBlockerType get_right_button_state_type() const { return EnforcerBlockerType::BLOCKER; } @@ -202,7 +203,7 @@ private: const Camera& camera, const std::vector& trafo_matrices) const; - GLIndexedVertexArray m_vbo_sphere; + static std::shared_ptr s_sphere; bool m_internal_stack_active = false; bool m_schedule_update = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 61fe6e7095..5046d19227 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -39,20 +39,6 @@ GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) { } -GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) - : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) - , m_axis(other.m_axis) - , m_angle(other.m_angle) - , m_center(other.m_center) - , m_radius(other.m_radius) - , m_snap_coarse_in_radius(other.m_snap_coarse_in_radius) - , m_snap_coarse_out_radius(other.m_snap_coarse_out_radius) - , m_snap_fine_in_radius(other.m_snap_fine_in_radius) - , m_snap_fine_out_radius(other.m_snap_fine_out_radius) -{ -} - - void GLGizmoRotate::set_angle(double angle) { if (std::abs(angle - 2.0 * (double)PI) < EPSILON) @@ -130,6 +116,9 @@ void GLGizmoRotate::on_render() if (!m_grabbers[0].enabled) return; + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); @@ -325,12 +314,9 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size); - std::array color = m_grabbers[0].color; - if (!picking && m_hover_id != -1) { - color[0] = 1.0f - color[0]; - color[1] = 1.0f - color[1]; - color[2] = 1.0f - color[2]; - } + ColorRGBA color = m_grabbers[0].color; + if (!picking && m_hover_id != -1) + color = complementary(color); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) @@ -433,11 +419,8 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) + , m_gizmos({ GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Z) }) { - m_gizmos.emplace_back(parent, GLGizmoRotate::X); - m_gizmos.emplace_back(parent, GLGizmoRotate::Y); - m_gizmos.emplace_back(parent, GLGizmoRotate::Z); - for (unsigned int i = 0; i < 3; ++i) { m_gizmos[i].set_group_id(i); } @@ -554,11 +537,12 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y}; ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x); - if (wxGetApp().plater()->is_any_job_running()) + if (!wxGetApp().plater()->get_ui_job_worker().is_idle()) imgui->disabled_begin(true); if ( imgui->button(btn_txt) ) { - wxGetApp().plater()->optimize_rotation(); + replace_job(wxGetApp().plater()->get_ui_job_worker(), + std::make_unique()); } imgui->disabled_end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 3245c4dbe8..448efd98a0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -2,8 +2,6 @@ #define slic3r_GLGizmoRotate_hpp_ #include "GLGizmoBase.hpp" -#include "../Jobs/RotoptimizeJob.hpp" - namespace Slic3r { namespace GUI { @@ -40,9 +38,10 @@ private: mutable float m_snap_fine_in_radius; mutable float m_snap_fine_out_radius; + GLModel m_cone; + public: GLGizmoRotate(GLCanvas3D& parent, Axis axis); - GLGizmoRotate(const GLGizmoRotate& other); virtual ~GLGizmoRotate() = default; double get_angle() const { return m_angle; } @@ -74,7 +73,7 @@ private: class GLGizmoRotate3D : public GLGizmoBase { - std::vector m_gizmos; + std::array m_gizmos; public: GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 9eba542733..d5f20adaa4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -77,6 +77,13 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S void GLGizmoSlaSupports::on_render() { + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); + if (!m_sphere.is_initialized()) + m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0)); + ModelObject* mo = m_c->selection_info()->model_object(); const Selection& selection = m_parent.get_selection(); @@ -137,7 +144,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) glsafe(::glTranslated(0.0, 0.0, z_shift)); glsafe(::glMultMatrixd(instance_matrix.data())); - std::array render_color; + ColorRGBA render_color; for (size_t i = 0; i < cache_size; ++i) { const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false; @@ -219,10 +226,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) // Now render the drain holes: if (has_holes && ! picking) { - render_color[0] = 0.7f; - render_color[1] = 0.7f; - render_color[2] = 0.7f; - render_color[3] = 0.7f; + render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; const_cast(&m_cylinder)->set_color(-1, render_color); if (shader) shader->set_uniform("emission_factor", 0.5f); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 35e6a73089..92d085f378 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -91,6 +91,10 @@ private: std::vector m_normal_cache; // to restore after discarding changes or undo/redo ObjectID m_old_mo_id; + GLModel m_cone; + GLModel m_cylinder; + GLModel m_sphere; + // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index d2a7e0d73e..cc53f267c0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -216,8 +216,8 @@ void InstancesHider::render_cut() const if (mv->is_model_part()) glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); else { - const std::array& c = color_from_model_volume(*mv); - glsafe(::glColor4f(c[0], c[1], c[2], c[3])); + const ColorRGBA color = color_from_model_volume(*mv); + glsafe(::glColor4fv(color.data())); } glsafe(::glPushAttrib(GL_DEPTH_TEST)); glsafe(::glDisable(GL_DEPTH_TEST)); diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index 291ce8f408..3e98374af9 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -417,9 +417,9 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) m_loaded_hints.emplace_back(hint_data); // open preferences } else if(dict["hypertext_type"] == "preferences") { - int page = static_cast(std::atoi(dict["hypertext_preferences_page"].c_str())); + std::string page = dict["hypertext_preferences_page"]; std::string item = dict["hypertext_preferences_item"]; - HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page, item]() { wxGetApp().open_preferences(page, item); } }; + HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page, item]() { wxGetApp().open_preferences(item, page); } }; m_loaded_hints.emplace_back(hint_data); } else if (dict["hypertext_type"] == "plater") { std::string item = dict["hypertext_plater_item"]; @@ -924,7 +924,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp } if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - wxGetApp().open_preferences(2, "show_hints"); + wxGetApp().open_preferences("show_hints", "GUI"); } ImGui::PopStyleColor(5); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e1563156b4..01eeef342c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -25,6 +25,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "3DScene.hpp" #include "GUI.hpp" #include "I18N.hpp" @@ -52,7 +53,22 @@ static const std::map font_icons = { {ImGui::PreferencesButton , "notification_preferences" }, {ImGui::PreferencesHoverButton, "notification_preferences_hover"}, {ImGui::SliderFloatEditBtnIcon, "edit_button" }, + {ImGui::SliderFloatEditBtnPressedIcon, "edit_button_pressed" }, +#if ENABLE_LEGEND_TOOLBAR_ICONS + {ImGui::LegendTravel , "legend_travel" }, + {ImGui::LegendWipe , "legend_wipe" }, + {ImGui::LegendRetract , "legend_retract" }, + {ImGui::LegendDeretract , "legend_deretract" }, + {ImGui::LegendSeams , "legend_seams" }, + {ImGui::LegendToolChanges , "legend_toolchanges" }, + {ImGui::LegendColorChanges , "legend_colorchanges" }, + {ImGui::LegendPausePrints , "legend_pauseprints" }, + {ImGui::LegendCustomGCodes , "legend_customgcodes" }, + {ImGui::LegendShells , "legend_shells" }, + {ImGui::LegendToolMarker , "legend_toolmarker" }, +#endif // ENABLE_LEGEND_TOOLBAR_ICONS }; + static const std::map font_icons_large = { {ImGui::CloseNotifButton , "notification_close" }, {ImGui::CloseNotifHoverButton , "notification_close_hover" }, @@ -78,14 +94,14 @@ static const std::map font_icons_extra_large = { }; -const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.33f, 0.33f, 0.33f, 1.0f }; const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.133f, 0.133f, 0.133f, 0.8f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.67f, 0.36f, 0.19f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = to_ImVec4(ColorRGBA::ORANGE()); +const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.13f, 0.13f, 0.13f, 0.8f }; const ImVec4 ImGuiWrapper::COL_BUTTON_BACKGROUND = COL_ORANGE_DARK; const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = COL_ORANGE_LIGHT; -const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED; +const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = COL_BUTTON_HOVERED; ImGuiWrapper::ImGuiWrapper() { @@ -370,10 +386,41 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active) return ImGui::RadioButton(label_utf8.c_str(), active); } -bool ImGuiWrapper::image_button() +#if ENABLE_PREVIEW_LAYOUT +bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool active, + std::function draw_callback) { - return false; + ImGuiWindow& window = *ImGui::GetCurrentWindow(); + if (window.SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window.GetID(name.c_str()); + + const ImVec2 pos = window.DC.CursorPos; + const ImRect total_bb(pos, pos + ImVec2(size, size + style.FramePadding.y * 2.0f)); + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + ImGui::MarkItemEdited(id); + + if (hovered) + window.DrawList->AddRect({ pos.x - 1.0f, pos.y - 1.0f }, { pos.x + size + 1.0f, pos.y + size + 1.0f }, ImGui::GetColorU32(ImGuiCol_CheckMark)); + + if (active) + window.DrawList->AddRect(pos, { pos.x + size, pos.y + size }, ImGui::GetColorU32(ImGuiCol_CheckMark)); + + draw_callback(window, pos, size); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window.DC.LastItemStatusFlags); + return pressed; } +#endif // ENABLE_PREVIEW_LAYOUT bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { @@ -497,6 +544,9 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float if (pos != std::string::npos) str_label = str_label.substr(0, pos) + str_label.substr(pos + 2); + // the current slider edit state needs to be detected here before calling SliderFloat() + bool slider_editing = ImGui::GetCurrentWindow()->GetID(str_label.c_str()) == ImGui::GetActiveID(); + bool ret = ImGui::SliderFloat(str_label.c_str(), v, v_min, v_max, format, power); m_last_slider_status.hovered = ImGui::IsItemHovered(); @@ -514,15 +564,42 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float if (show_edit_btn) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); ImGui::SameLine(); - std::wstring btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(str_label); + +#if ENABLE_LEGEND_TOOLBAR_ICONS + ImGuiIO& io = ImGui::GetIO(); + assert(io.Fonts->TexWidth > 0 && io.Fonts->TexHeight > 0); + float inv_tex_w = 1.0f / float(io.Fonts->TexWidth); + float inv_tex_h = 1.0f / float(io.Fonts->TexHeight); + + const ImFontAtlasCustomRect* const rect = GetTextureCustomRect(slider_editing ? ImGui::SliderFloatEditBtnPressedIcon : ImGui::SliderFloatEditBtnIcon); + const ImVec2 size = { float(rect->Width), float(rect->Height) }; + const ImVec2 uv0 = ImVec2(float(rect->X) * inv_tex_w, float(rect->Y) * inv_tex_h); + const ImVec2 uv1 = ImVec2(float(rect->X + rect->Width) * inv_tex_w, float(rect->Y + rect->Height) * inv_tex_h); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f }); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.5f, 0.5f, 0.5f, 1.0f }); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.5f, 0.5f, 0.5f, 1.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.4f, 0.4f, 0.4f, 1.0f }); + +#if ENABLE_LEGEND_TOOLBAR_ICONS + const ImTextureID tex_id = io.Fonts->TexID; + if (image_button(tex_id, size, uv0, uv1, -1, ImVec4(0.0, 0.0, 0.0, 0.0), ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags_PressedOnClick)) { + if (!slider_editing) + ImGui::SetKeyboardFocusHere(-1); + else + ImGui::ClearActiveID(); + this->set_requires_extra_frame(); + } +#else + std::wstring btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(str_label); if (ImGui::Button(into_u8(btn_name).c_str())) { ImGui::SetKeyboardFocusHere(-1); this->set_requires_extra_frame(); } +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + ImGui::PopStyleColor(3); + if (ImGui::IsItemHovered()) this->tooltip(into_u8(_L("Edit")).c_str(), max_tooltip_width); @@ -556,17 +633,65 @@ bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, fl return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power, clamp, tooltip, show_edit_btn); } -bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection) +static bool image_button_ex(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); + ImGui::ItemSize(bb); + if (!ImGui::ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, ImGui::GetColorU32(bg_col)); + window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, ImGui::GetColorU32(tint_col)); + + return pressed; +} + +bool ImGuiWrapper::image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + ImGui::PushID((void*)(intptr_t)user_texture_id); + const ImGuiID id = window->GetID("#image"); + ImGui::PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding; + return image_button_ex(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col, flags); +} + +bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags) { // this is to force the label to the left of the widget: - text(label); - ImGui::SameLine(); +#if ENABLE_PREVIEW_LAYOUT + if (!label.empty()) { +#endif // ENABLE_PREVIEW_LAYOUT + text(label); + ImGui::SameLine(); +#if ENABLE_PREVIEW_LAYOUT + } +#endif // ENABLE_PREVIEW_LAYOUT int selection_out = selection; bool res = false; const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : ""; - if (ImGui::BeginCombo("", selection_str)) { + if (ImGui::BeginCombo("", selection_str, flags)) { for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { selection_out = i; @@ -1010,6 +1135,34 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } +#if ENABLE_LEGEND_TOOLBAR_ICONS +ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id) +{ + auto item = m_custom_glyph_rects_ids.find(tex_id); + return (item != m_custom_glyph_rects_ids.end()) ? ImGui::GetIO().Fonts->GetCustomRectByIndex(m_custom_glyph_rects_ids[tex_id]) : nullptr; +} +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + +ImU32 ImGuiWrapper::to_ImU32(const ColorRGBA& color) +{ + return ImGui::GetColorU32({ color.r(), color.g(), color.b(), color.a() }); +} + +ImVec4 ImGuiWrapper::to_ImVec4(const ColorRGBA& color) +{ + return { color.r(), color.g(), color.b(), color.a() }; +} + +ColorRGBA ImGuiWrapper::from_ImU32(const ImU32& color) +{ + return from_ImVec4(ImGui::ColorConvertU32ToFloat4(color)); +} + +ColorRGBA ImGuiWrapper::from_ImVec4(const ImVec4& color) +{ + return { color.x, color.y, color.z, color.w }; +} + #ifdef __APPLE__ static const ImWchar ranges_keyboard_shortcuts[] = { @@ -1098,12 +1251,27 @@ void ImGuiWrapper::init_font(bool compress) int rect_id = io.Fonts->CustomRects.Size; // id of the rectangle added next // add rectangles for the icons to the font atlas +#if ENABLE_LEGEND_TOOLBAR_ICONS + for (auto& icon : font_icons) { + m_custom_glyph_rects_ids[icon.first] = + io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); + } + for (auto& icon : font_icons_large) { + m_custom_glyph_rects_ids[icon.first] = + io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); + } + for (auto& icon : font_icons_extra_large) { + m_custom_glyph_rects_ids[icon.first] = + io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); +} +#else for (auto& icon : font_icons) io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); for (auto& icon : font_icons_large) io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); for (auto& icon : font_icons_extra_large) io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS // Build texture atlas unsigned char* pixels; diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index d75d6529ab..f461bc970a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -9,15 +9,21 @@ #include #include "libslic3r/Point.hpp" +#include "libslic3r/Color.hpp" -namespace Slic3r {namespace Search { +namespace Slic3r { +namespace Search { struct OptionViewParameters; -}} +} // namespace Search +} // namespace Slic3r class wxString; class wxMouseEvent; class wxKeyEvent; +#if ENABLE_PREVIEW_LAYOUT +struct IMGUI_API ImGuiWindow; +#endif // ENABLE_PREVIEW_LAYOUT namespace Slic3r { namespace GUI { @@ -34,6 +40,9 @@ class ImGuiWrapper bool m_disabled{ false }; bool m_new_frame_open{ false }; bool m_requires_extra_frame{ false }; +#if ENABLE_LEGEND_TOOLBAR_ICONS + std::map m_custom_glyph_rects_ids; +#endif // ENABLE_LEGEND_TOOLBAR_ICONS std::string m_clipboard_text; public: @@ -83,7 +92,9 @@ public: bool button(const wxString &label); bool button(const wxString& label, float width, float height); bool radio_button(const wxString &label, bool active); - bool image_button(); +#if ENABLE_PREVIEW_LAYOUT + bool draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback); +#endif // ENABLE_PREVIEW_LAYOUT bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); @@ -106,7 +117,10 @@ public: bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true); bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true); - bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected + bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0.0, 0.0), const ImVec2& uv1 = ImVec2(1.0, 1.0), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0.0, 0.0, 0.0, 0.0), const ImVec4& tint_col = ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags flags = 0); + + // Use selection = -1 to not mark any option as selected + bool combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags = 0); bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel); void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str, Search::OptionViewParameters& view_params, int& selected, bool& edited, int& mouse_wheel, bool is_localized); @@ -124,6 +138,15 @@ public: void set_requires_extra_frame() { m_requires_extra_frame = true; } void reset_requires_extra_frame() { m_requires_extra_frame = false; } + static ImU32 to_ImU32(const ColorRGBA& color); + static ImVec4 to_ImVec4(const ColorRGBA& color); + static ColorRGBA from_ImU32(const ImU32& color); + static ColorRGBA from_ImVec4(const ImVec4& color); + +#if ENABLE_LEGEND_TOOLBAR_ICONS + ImFontAtlasCustomRect* GetTextureCustomRect(const wchar_t& tex_id); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + static const ImVec4 COL_GREY_DARK; static const ImVec4 COL_GREY_LIGHT; static const ImVec4 COL_ORANGE_DARK; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 2771f9d271..3c7dad0a61 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -162,49 +162,43 @@ void ArrangeJob::prepare() wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); } -void ArrangeJob::on_exception(const std::exception_ptr &eptr) +void ArrangeJob::process(Ctl &ctl) { - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (libnest2d::GeometryException &) { - show_error(m_plater, _(L("Could not arrange model objects! " - "Some geometries may be invalid."))); - } catch (std::exception &) { - PlaterJob::on_exception(eptr); - } -} + static const auto arrangestr = _u8L("Arranging"); -void ArrangeJob::process() -{ - static const auto arrangestr = _(L("Arranging")); + ctl.update_status(0, arrangestr); + ctl.call_on_main_thread([this]{ prepare(); }).wait();; arrangement::ArrangeParams params = get_arrange_params(m_plater); - auto count = unsigned(m_selected.size() + m_unprintable.size()); + auto count = unsigned(m_selected.size() + m_unprintable.size()); Points bedpts = get_bed_shape(*m_plater->config()); - - params.stopcondition = [this]() { return was_canceled(); }; - - params.progressind = [this, count](unsigned st) { + + params.stopcondition = [&ctl]() { return ctl.was_canceled(); }; + + params.progressind = [this, count, &ctl](unsigned st) { st += m_unprintable.size(); - if (st > 0) update_status(int(count - st), arrangestr); + if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr); }; + ctl.update_status(0, arrangestr); + arrangement::arrange(m_selected, m_unselected, bedpts, params); - params.progressind = [this, count](unsigned st) { - if (st > 0) update_status(int(count - st), arrangestr); + params.progressind = [this, count, &ctl](unsigned st) { + if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr); }; arrangement::arrange(m_unprintable, {}, bedpts, params); // finalize just here. - update_status(int(count), - was_canceled() ? _(L("Arranging canceled.")) - : _(L("Arranging done."))); + ctl.update_status(int(count) * 100 / status_range(), ctl.was_canceled() ? + _u8L("Arranging canceled.") : + _u8L("Arranging done.")); } +ArrangeJob::ArrangeJob() : m_plater{wxGetApp().plater()} {} + static std::string concat_strings(const std::set &strings, const std::string &delim = "\n") { @@ -215,10 +209,21 @@ static std::string concat_strings(const std::set &strings, }); } -void ArrangeJob::finalize() { - // Ignore the arrange result if aborted. - if (was_canceled()) return; - +void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { + try { + if (eptr) + std::rethrow_exception(eptr); + } catch (libnest2d::GeometryException &) { + show_error(m_plater, _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); + eptr = nullptr; + } catch(...) { + eptr = std::current_exception(); + } + + if (canceled || eptr) + return; + // Unprintable items go to the last virtual bed int beds = 0; @@ -250,8 +255,6 @@ void ArrangeJob::finalize() { _L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"), concat_strings(names, "\n"))); } - - Job::finalize(); } std::optional diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index a5ecc0c834..106cc57ddf 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -1,7 +1,9 @@ #ifndef ARRANGEJOB_HPP #define ARRANGEJOB_HPP -#include "PlaterJob.hpp" +#include + +#include "Job.hpp" #include "libslic3r/Arrange.hpp" namespace Slic3r { @@ -10,13 +12,16 @@ class ModelInstance; namespace GUI { -class ArrangeJob : public PlaterJob +class Plater; + +class ArrangeJob : public Job { using ArrangePolygon = arrangement::ArrangePolygon; using ArrangePolygons = arrangement::ArrangePolygons; ArrangePolygons m_selected, m_unselected, m_unprintable; std::vector m_unarranged; + Plater *m_plater; // clear m_selected and m_unselected, reserve space for next usage void clear_input(); @@ -30,25 +35,20 @@ class ArrangeJob : public PlaterJob ArrangePolygon get_arrange_poly_(ModelInstance *mi); -protected: - - void prepare() override; - - void on_exception(const std::exception_ptr &) override; - - void process() override; - public: - ArrangeJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} - int status_range() const override + void prepare(); + + void process(Ctl &ctl) override; + + ArrangeJob(); + + int status_range() const { return int(m_selected.size() + m_unprintable.size()); } - void finalize() override; + void finalize(bool canceled, std::exception_ptr &e) override; }; std::optional get_wipe_tower_arrangepoly(const Plater &); diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp new file mode 100644 index 0000000000..a4f22be559 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp @@ -0,0 +1,181 @@ +#include + +#include "BoostThreadWorker.hpp" + +namespace Slic3r { namespace GUI { + +void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner) +{ + switch(MsgType(get_type())) { + case Empty: break; + case Status: { + auto info = boost::get(m_data); + if (runner.get_pri()) { + runner.get_pri()->set_progress(info.status); + runner.get_pri()->set_status_text(info.msg.c_str()); + } + break; + } + case Finalize: { + auto& entry = boost::get(m_data); + entry.job->finalize(entry.canceled, entry.eptr); + + // Unhandled exceptions are rethrown without mercy. + if (entry.eptr) + std::rethrow_exception(entry.eptr); + + break; + } + case MainThreadCall: { + auto &calldata = boost::get(m_data); + calldata.fn(); + calldata.promise.set_value(); + + break; + } + } +} + +void BoostThreadWorker::run() +{ + bool stop = false; + while (!stop) { + m_input_queue + .consume_one(BlockingWait{0, &m_running}, [this, &stop](JobEntry &e) { + if (!e.job) + stop = true; + else { + m_canceled.store(false); + + try { + e.job->process(*this); + } catch (...) { + e.eptr = std::current_exception(); + } + + e.canceled = m_canceled.load(); + m_output_queue.push(std::move(e)); // finalization message + } + m_running.store(false); + }); + }; +} + +void BoostThreadWorker::update_status(int st, const std::string &msg) +{ + m_output_queue.push(st, msg); +} + +std::future BoostThreadWorker::call_on_main_thread(std::function fn) +{ + MainThreadCallData cbdata{std::move(fn), {}}; + std::future future = cbdata.promise.get_future(); + + m_output_queue.push(std::move(cbdata)); + + return future; +} + +BoostThreadWorker::BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes &attribs, + const char * name) + : m_progress(std::move(pri)), m_name{name} +{ + if (m_progress) + m_progress->set_cancel_callback([this](){ cancel(); }); + + m_thread = create_thread(attribs, [this] { this->run(); }); + + std::string nm{name}; + if (!nm.empty()) set_thread_name(m_thread, name); +} + +constexpr int ABORT_WAIT_MAX_MS = 10000; + +BoostThreadWorker::~BoostThreadWorker() +{ + bool joined = false; + try { + cancel_all(); + wait_for_idle(ABORT_WAIT_MAX_MS); + m_input_queue.push(JobEntry{nullptr}); + joined = join(ABORT_WAIT_MAX_MS); + } catch(...) {} + + if (!joined) + BOOST_LOG_TRIVIAL(error) + << "Could not join worker thread '" << m_name << "'"; +} + +bool BoostThreadWorker::join(int timeout_ms) +{ + if (!m_thread.joinable()) + return true; + + if (timeout_ms <= 0) { + m_thread.join(); + } + else if (m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) { + return true; + } + else + return false; + + return true; +} + +void BoostThreadWorker::process_events() +{ + while (m_output_queue.consume_one([this](WorkerMessage &msg) { + msg.deliver(*this); + })); +} + +bool BoostThreadWorker::wait_for_current_job(unsigned timeout_ms) +{ + bool ret = true; + + if (!is_idle()) { + bool was_finish = false; + bool timeout_reached = false; + while (!timeout_reached && !was_finish) { + timeout_reached = + !m_output_queue.consume_one(BlockingWait{timeout_ms}, + [this, &was_finish]( + WorkerMessage &msg) { + msg.deliver(*this); + if (msg.get_type() == + WorkerMessage::Finalize) + was_finish = true; + }); + } + + ret = !timeout_reached; + } + + return ret; +} + +bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms) +{ + bool timeout_reached = false; + while (!timeout_reached && !is_idle()) { + timeout_reached = !m_output_queue + .consume_one(BlockingWait{timeout_ms}, + [this](WorkerMessage &msg) { + msg.deliver(*this); + }); + } + + return !timeout_reached; +} + +bool BoostThreadWorker::push(std::unique_ptr job) +{ + if (job) + m_input_queue.push(JobEntry{std::move(job)}); + + return bool{job}; +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp new file mode 100644 index 0000000000..2fe39c0a7b --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp @@ -0,0 +1,140 @@ +#ifndef BOOSTTHREADWORKER_HPP +#define BOOSTTHREADWORKER_HPP + +#include + +#include "Worker.hpp" + +#include +#include + +#include "ThreadSafeQueue.hpp" + +namespace Slic3r { namespace GUI { + +// An implementation of the Worker interface which uses the boost::thread +// API and two thread safe message queues to communicate with the main thread +// back and forth. The queue from the main thread to the worker thread holds the +// job entries that will be performed on the worker. The other queue holds messages +// from the worker to the main thread. These messages include status updates, +// finishing operation and arbitrary functiors that need to be performed +// on the main thread during the jobs execution, like displaying intermediate +// results. +class BoostThreadWorker : public Worker, private Job::Ctl +{ + struct JobEntry // Goes into worker and also out of worker as a finalize msg + { + std::unique_ptr job; + bool canceled = false; + std::exception_ptr eptr = nullptr; + }; + + // A message data for status updates. Only goes from worker to main thread. + struct StatusInfo { int status; std::string msg; }; + + // An arbitrary callback to be called on the main thread. Only from worker + // to main thread. + struct MainThreadCallData + { + std::function fn; + std::promise promise; + }; + + struct EmptyMessage {}; + + class WorkerMessage + { + public: + enum MsgType { Empty, Status, Finalize, MainThreadCall }; + + private: + boost::variant m_data; + + public: + WorkerMessage() = default; + WorkerMessage(int s, std::string txt) + : m_data{StatusInfo{s, std::move(txt)}} + {} + WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {} + WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {} + + int get_type () const { return m_data.which(); } + + void deliver(BoostThreadWorker &runner); + }; + + using JobQueue = ThreadSafeQueueSPSC; + using MessageQueue = ThreadSafeQueueSPSC; + + boost::thread m_thread; + std::atomic m_running{false}, m_canceled{false}; + std::shared_ptr m_progress; + JobQueue m_input_queue; // from main thread to worker + MessageQueue m_output_queue; // form worker to main thread + std::string m_name; + + void run(); + + bool join(int timeout_ms = 0); + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override; + + bool was_canceled() const override { return m_canceled.load(); } + + std::future call_on_main_thread(std::function fn) override; + +public: + explicit BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes & attr, + const char * name = ""); + + explicit BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes && attr, + const char * name = "") + : BoostThreadWorker{std::move(pri), attr, name} + {} + + explicit BoostThreadWorker(std::shared_ptr pri, + const char * name = "") + : BoostThreadWorker{std::move(pri), {}, name} + {} + + ~BoostThreadWorker(); + + BoostThreadWorker(const BoostThreadWorker &) = delete; + BoostThreadWorker(BoostThreadWorker &&) = delete; + BoostThreadWorker &operator=(const BoostThreadWorker &) = delete; + BoostThreadWorker &operator=(BoostThreadWorker &&) = delete; + + bool push(std::unique_ptr job) override; + + bool is_idle() const override + { + // The assumption is that jobs can only be queued from a single main + // thread from which this method is also called. And the output + // messages are also processed only in this calling thread. In that + // case, if the input queue is empty, it will remain so during this + // function call. If the worker thread is also not running and the + // output queue is already processed, we can safely say that the + // worker is dormant. + return m_input_queue.empty() && !m_running.load() && m_output_queue.empty(); + } + + void cancel() override { m_canceled.store(true); } + void cancel_all() override { m_input_queue.clear(); cancel(); } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } + + void process_events() override; + bool wait_for_current_job(unsigned timeout_ms = 0) override; + bool wait_for_idle(unsigned timeout_ms = 0) override; + +}; + +}} // namespace Slic3r::GUI + +#endif // BOOSTTHREADWORKER_HPP diff --git a/src/slic3r/GUI/Jobs/BusyCursorJob.hpp b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp new file mode 100644 index 0000000000..530213b1d5 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp @@ -0,0 +1,48 @@ +#ifndef BUSYCURSORJOB_HPP +#define BUSYCURSORJOB_HPP + +#include "Job.hpp" + +#include + +namespace Slic3r { namespace GUI { + +struct CursorSetterRAII +{ + Job::Ctl &ctl; + CursorSetterRAII(Job::Ctl &c) : ctl{c} + { + ctl.call_on_main_thread([] { wxBeginBusyCursor(); }); + } + ~CursorSetterRAII() + { + ctl.call_on_main_thread([] { wxEndBusyCursor(); }); + } +}; + +template +class BusyCursored: public Job { + JobSubclass m_job; + +public: + template + BusyCursored(Args &&...args) : m_job{std::forward(args)...} + {} + + void process(Ctl &ctl) override + { + CursorSetterRAII cursor_setter{ctl}; + m_job.process(ctl); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + m_job.finalize(canceled, eptr); + } +}; + + +} +} + +#endif // BUSYCURSORJOB_HPP diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 870f31f2fc..c7d69eb500 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -3,6 +3,7 @@ #include "libslic3r/Model.hpp" #include "libslic3r/ClipperUtils.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" @@ -102,8 +103,12 @@ void FillBedJob::prepare() p.translation(X) -= p.bed_idx * stride; } -void FillBedJob::process() +void FillBedJob::process(Ctl &ctl) { + auto statustxt = _u8L("Filling bed"); + ctl.call_on_main_thread([this] { prepare(); }).wait(); + ctl.update_status(0, statustxt); + if (m_object_idx == -1 || m_selected.empty()) return; const GLCanvas3D::ArrangeSettings &settings = @@ -114,13 +119,13 @@ void FillBedJob::process() params.min_obj_distance = scaled(settings.distance); bool do_stop = false; - params.stopcondition = [this, &do_stop]() { - return was_canceled() || do_stop; + params.stopcondition = [&ctl, &do_stop]() { + return ctl.was_canceled() || do_stop; }; - params.progressind = [this](unsigned st) { + params.progressind = [this, &ctl, &statustxt](unsigned st) { if (st > 0) - update_status(int(m_status_range - st), _(L("Filling bed"))); + ctl.update_status(int(m_status_range - st) * 100 / status_range(), statustxt); }; params.on_packed = [&do_stop] (const ArrangePolygon &ap) { @@ -130,15 +135,18 @@ void FillBedJob::process() arrangement::arrange(m_selected, m_unselected, m_bedpts, params); // finalize just here. - update_status(m_status_range, was_canceled() ? - _(L("Bed filling canceled.")) : - _(L("Bed filling done."))); + ctl.update_status(100, ctl.was_canceled() ? + _u8L("Bed filling canceled.") : + _u8L("Bed filling done.")); } -void FillBedJob::finalize() +FillBedJob::FillBedJob() : m_plater{wxGetApp().plater()} {} + +void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) { // Ignore the arrange result if aborted. - if (was_canceled()) return; + if (canceled || eptr) + return; if (m_object_idx == -1) return; @@ -167,8 +175,6 @@ void FillBedJob::finalize() m_plater->sidebar() .obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt)); } - - Job::finalize(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index bf407656d1..b1417bbbd4 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -7,9 +7,9 @@ namespace Slic3r { namespace GUI { class Plater; -class FillBedJob : public PlaterJob +class FillBedJob : public Job { - int m_object_idx = -1; + int m_object_idx = -1; using ArrangePolygon = arrangement::ArrangePolygon; using ArrangePolygons = arrangement::ArrangePolygons; @@ -20,23 +20,20 @@ class FillBedJob : public PlaterJob Points m_bedpts; int m_status_range = 0; - -protected: - - void prepare() override; - void process() override; + Plater *m_plater; public: - FillBedJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + void prepare(); + void process(Ctl &ctl) override; - int status_range() const override + FillBedJob(); + + int status_range() const /*override*/ { return m_status_range; } - void finalize() override; + void finalize(bool canceled, std::exception_ptr &e) override; }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp deleted file mode 100644 index 9d0d4bc80c..0000000000 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include -#include - -#include "Job.hpp" -#include -#include - -namespace Slic3r { - -void GUI::Job::run(std::exception_ptr &eptr) -{ - m_running.store(true); - try { - process(); - } catch (...) { - eptr = std::current_exception(); - } - - m_running.store(false); - - // ensure to call the last status to finalize the job - update_status(status_range(), ""); -} - -void GUI::Job::update_status(int st, const wxString &msg) -{ - auto evt = new wxThreadEvent(wxEVT_THREAD, m_thread_evt_id); - evt->SetInt(st); - evt->SetString(msg); - wxQueueEvent(this, evt); -} - -GUI::Job::Job(std::shared_ptr pri) - : m_progress(std::move(pri)) -{ - m_thread_evt_id = wxNewId(); - - Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { - if (m_finalizing) return; - - auto msg = evt.GetString(); - if (!msg.empty() && !m_worker_error) - m_progress->set_status_text(msg.ToUTF8().data()); - - if (m_finalized) return; - - m_progress->set_progress(evt.GetInt()); - if (evt.GetInt() == status_range() || m_worker_error) { - // set back the original range and cancel callback - m_progress->set_range(m_range); - // Make sure progress indicators get the last value of their range - // to make sure they close, fade out, whathever - m_progress->set_progress(m_range); - m_progress->set_cancel_callback(); - wxEndBusyCursor(); - - if (m_worker_error) { - m_finalized = true; - m_progress->set_status_text(""); - m_progress->set_progress(m_range); - on_exception(m_worker_error); - } - else { - // This is an RAII solution to remember that finalization is - // running. The run method calls update_status(status_range(), "") - // at the end, which queues up a call to this handler in all cases. - // If process also calls update_status with maxed out status arg - // it will call this handler twice. It is not a problem unless - // yield is called inside the finilize() method, which would - // jump out of finalize and call this handler again. - struct Finalizing { - bool &flag; - Finalizing (bool &f): flag(f) { flag = true; } - ~Finalizing() { flag = false; } - } fin(m_finalizing); - - finalize(); - } - - // dont do finalization again for the same process - m_finalized = true; - } - }, m_thread_evt_id); -} - -void GUI::Job::start() -{ // Start the job. No effect if the job is already running - if (!m_running.load()) { - prepare(); - - // Save the current status indicatior range and push the new one - m_range = m_progress->get_range(); - m_progress->set_range(status_range()); - - // init cancellation flag and set the cancel callback - m_canceled.store(false); - m_progress->set_cancel_callback( - [this]() { m_canceled.store(true); }); - - m_finalized = false; - m_finalizing = false; - - // Changing cursor to busy - wxBeginBusyCursor(); - - try { // Execute the job - m_worker_error = nullptr; - m_thread = create_thread([this] { this->run(m_worker_error); }); - } catch (std::exception &) { - update_status(status_range(), - _(L("ERROR: not enough resources to " - "execute a new job."))); - } - - // The state changes will be undone when the process hits the - // last status value, in the status update handler (see ctor) - } -} - -bool GUI::Job::join(int timeout_ms) -{ - if (!m_thread.joinable()) return true; - - if (timeout_ms <= 0) - m_thread.join(); - else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) - return false; - - return true; -} - -void GUI::ExclusiveJobGroup::start(size_t jid) { - assert(jid < m_jobs.size()); - stop_all(); - m_jobs[jid]->start(); -} - -void GUI::ExclusiveJobGroup::join_all(int wait_ms) -{ - std::vector aborted(m_jobs.size(), false); - - for (size_t jid = 0; jid < m_jobs.size(); ++jid) - aborted[jid] = m_jobs[jid]->join(wait_ms); - - if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; })) - BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; -} - -bool GUI::ExclusiveJobGroup::is_any_running() const -{ - return std::any_of(m_jobs.begin(), m_jobs.end(), - [](const std::unique_ptr &j) { - return j->is_running(); - }); -} - -} - diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index 8243ce9430..824c0b8303 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -3,119 +3,53 @@ #include #include +#include #include "libslic3r/libslic3r.h" - -#include - #include "ProgressIndicator.hpp" -#include - -#include - namespace Slic3r { namespace GUI { -// A class to handle UI jobs like arranging and optimizing rotation. -// These are not instant jobs, the user has to be informed about their -// state in the status progress indicator. On the other hand they are -// separated from the background slicing process. Ideally, these jobs should -// run when the background process is not running. -// -// TODO: A mechanism would be useful for blocking the plater interactions: -// objects would be frozen for the user. In case of arrange, an animation -// could be shown, or with the optimize orientations, partial results -// could be displayed. -class Job : public wxEvtHandler -{ - int m_range = 100; - int m_thread_evt_id = wxID_ANY; - boost::thread m_thread; - std::atomic m_running{false}, m_canceled{false}; - bool m_finalized = false, m_finalizing = false; - std::shared_ptr m_progress; - std::exception_ptr m_worker_error = nullptr; - - void run(std::exception_ptr &); - -protected: - // status range for a particular job - virtual int status_range() const { return 100; } - - // status update, to be used from the work thread (process() method) - void update_status(int st, const wxString &msg = ""); +// A class representing a job that is to be run in the background, not blocking +// the main thread. Running it is up to a Worker object (see Worker interface) +class Job { +public: - bool was_canceled() const { return m_canceled.load(); } + // A controller interface that informs the job about cancellation and + // makes it possible for the job to advertise its status. + class Ctl { + public: + virtual ~Ctl() = default; - // Launched just before start(), a job can use it to prepare internals - virtual void prepare() {} + // status update, to be used from the work thread (process() method) + virtual void update_status(int st, const std::string &msg = "") = 0; - // The method where the actual work of the job should be defined. - virtual void process() = 0; - - // Launched when the job is finished. It refreshes the 3Dscene by def. - virtual void finalize() { m_finalized = true; } + // Returns true if the job was asked to cancel itself. + virtual bool was_canceled() const = 0; + // Execute a functor on the main thread. Note that the exact time of + // execution is hard to determine. This can be used to make modifications + // on the UI, like displaying some intermediate results or modify the + // cursor. + // This function returns a std::future object which enables the + // caller to optionally wait for the main thread to finish the function call. + virtual std::future call_on_main_thread(std::function fn) = 0; + }; + + virtual ~Job() = default; + + // The method where the actual work of the job should be defined. This is + // run on the worker thread. + virtual void process(Ctl &ctl) = 0; + + // Launched when the job is finished on the UI thread. + // If the job was cancelled, the first parameter will have a true value. // Exceptions occuring in process() are redirected from the worker thread - // into the main (UI) thread. This method is called from the main thread and - // can be overriden to handle these exceptions. - virtual void on_exception(const std::exception_ptr &eptr) - { - if (eptr) std::rethrow_exception(eptr); - } - -public: - Job(std::shared_ptr pri); - - bool is_finalized() const { return m_finalized; } - - Job(const Job &) = delete; - Job(Job &&) = delete; - Job &operator=(const Job &) = delete; - Job &operator=(Job &&) = delete; - - void start(); - - // To wait for the running job and join the threads. False is - // returned if the timeout has been reached and the job is still - // running. Call cancel() before this fn if you want to explicitly - // end the job. - bool join(int timeout_ms = 0); - - bool is_running() const { return m_running.load(); } - void cancel() { m_canceled.store(true); } -}; - -// Jobs defined inside the group class will be managed so that only one can -// run at a time. Also, the background process will be stopped if a job is -// started. -class ExclusiveJobGroup -{ - static const int ABORT_WAIT_MAX_MS = 10000; - - std::vector> m_jobs; - -protected: - virtual void before_start() {} - -public: - virtual ~ExclusiveJobGroup() = default; - - size_t add_job(std::unique_ptr &&job) - { - m_jobs.emplace_back(std::move(job)); - return m_jobs.size() - 1; - } - - void start(size_t jid); - - void cancel_all() { for (auto& j : m_jobs) j->cancel(); } - - void join_all(int wait_ms = 0); - - void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } - - bool is_any_running() const; + // into the main (UI) thread. This method receives the exception and can + // handle it properly. Assign nullptr to this second argument before + // function return to prevent further action. Leaving it with a non-null + // value will result in rethrowing by the worker. + virtual void finalize(bool /*canceled*/, std::exception_ptr &) {} }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp index cb71705687..f398f73339 100644 --- a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp @@ -12,11 +12,15 @@ void NotificationProgressIndicator::set_range(int range) void NotificationProgressIndicator::set_cancel_callback(CancelFn fn) { - m_nm->progress_indicator_set_cancel_callback(std::move(fn)); + m_cancelfn = std::move(fn); + m_nm->progress_indicator_set_cancel_callback(m_cancelfn); } void NotificationProgressIndicator::set_progress(int pr) { + if (!pr) + set_cancel_callback(m_cancelfn); + m_nm->progress_indicator_set_progress(pr); } diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp index 6b03af69df..b31cb7f7cd 100644 --- a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp @@ -9,7 +9,7 @@ class NotificationManager; class NotificationProgressIndicator: public ProgressIndicator { NotificationManager *m_nm = nullptr; - + CancelFn m_cancelfn; public: explicit NotificationProgressIndicator(NotificationManager *nm); diff --git a/src/slic3r/GUI/Jobs/PlaterJob.cpp b/src/slic3r/GUI/Jobs/PlaterJob.cpp deleted file mode 100644 index 4af205d41b..0000000000 --- a/src/slic3r/GUI/Jobs/PlaterJob.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "PlaterJob.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/Plater.hpp" - -namespace Slic3r { namespace GUI { - -void PlaterJob::on_exception(const std::exception_ptr &eptr) -{ - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (std::exception &e) { - show_error(m_plater, _(L("An unexpected error occured")) + ": "+ e.what()); - } -} - -}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/PlaterJob.hpp b/src/slic3r/GUI/Jobs/PlaterJob.hpp deleted file mode 100644 index fcf0a54b80..0000000000 --- a/src/slic3r/GUI/Jobs/PlaterJob.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PLATERJOB_HPP -#define PLATERJOB_HPP - -#include "Job.hpp" - -namespace Slic3r { namespace GUI { - -class Plater; - -class PlaterJob : public Job { -protected: - Plater *m_plater; - - void on_exception(const std::exception_ptr &) override; - -public: - - PlaterJob(std::shared_ptr pri, Plater *plater): - Job{std::move(pri)}, m_plater{plater} {} -}; - -}} // namespace Slic3r::GUI - -#endif // PLATERJOB_HPP diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp new file mode 100644 index 0000000000..5735902728 --- /dev/null +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -0,0 +1,127 @@ +#ifndef PLATERWORKER_HPP +#define PLATERWORKER_HPP + +#include + +#include "Worker.hpp" +#include "BusyCursorJob.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +template +class PlaterWorker: public Worker { + WorkerSubclass m_w; + Plater *m_plater; + + class PlaterJob : public Job { + std::unique_ptr m_job; + Plater *m_plater; + + public: + void process(Ctl &c) override + { + // Ensure that wxWidgets processing wakes up to handle outgoing + // messages in plater's wxIdle handler. Otherwise it might happen + // that the message will only be processed when an event like mouse + // move comes along which might be too late. + struct WakeUpCtl: Ctl { + Ctl &ctl; + WakeUpCtl(Ctl &c) : ctl{c} {} + + void update_status(int st, const std::string &msg = "") override + { + wxWakeUpIdle(); + ctl.update_status(st, msg); + } + + bool was_canceled() const override { return ctl.was_canceled(); } + + std::future call_on_main_thread(std::function fn) override + { + wxWakeUpIdle(); + return ctl.call_on_main_thread(std::move(fn)); + } + + } wctl{c}; + + CursorSetterRAII busycursor{wctl}; + m_job->process(wctl); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + m_job->finalize(canceled, eptr); + + if (eptr) try { + std::rethrow_exception(eptr); + } catch (std::exception &e) { + show_error(m_plater, _L("An unexpected error occured: ") + e.what()); + eptr = nullptr; + } + } + + PlaterJob(Plater *p, std::unique_ptr j) + : m_job{std::move(j)}, m_plater{p} + { + // TODO: decide if disabling slice button during UI job is what we + // want. + // if (m_plater) + // m_plater->sidebar().enable_buttons(false); + } + + ~PlaterJob() override + { + // TODO: decide if disabling slice button during UI job is what we want. + + // Reload scene ensures that the slice button gets properly + // enabled or disabled after the job finishes, depending on the + // state of slicing. This might be an overkill but works for now. + // if (m_plater) + // m_plater->canvas3D()->reload_scene(false); + } + }; + +public: + + template + PlaterWorker(Plater *plater, WorkerArgs &&...args) + : m_w{std::forward(args)...}, m_plater{plater} + { + // Ensure that messages from the worker thread to the UI thread are + // processed continuously. + plater->Bind(wxEVT_IDLE, [this](wxIdleEvent &) { + process_events(); + }); + } + + // Always package the job argument into a PlaterJob + bool push(std::unique_ptr job) override + { + return m_w.push(std::make_unique(m_plater, std::move(job))); + } + + bool is_idle() const override { return m_w.is_idle(); } + void cancel() override { m_w.cancel(); } + void cancel_all() override { m_w.cancel_all(); } + void process_events() override { m_w.process_events(); } + bool wait_for_current_job(unsigned timeout_ms = 0) override + { + return m_w.wait_for_current_job(timeout_ms); + } + bool wait_for_idle(unsigned timeout_ms = 0) override + { + return m_w.wait_for_idle(timeout_ms); + } +}; + +}} // namespace Slic3r::GUI + +#endif // PLATERJOB_HPP diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 95821a674d..e88d24fcdc 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -12,6 +12,8 @@ #include "slic3r/GUI/GUI_App.hpp" #include "libslic3r/AppConfig.hpp" +#include + namespace Slic3r { namespace GUI { void RotoptimizeJob::prepare() @@ -45,20 +47,23 @@ void RotoptimizeJob::prepare() } } -void RotoptimizeJob::process() +void RotoptimizeJob::process(Ctl &ctl) { int prev_status = 0; + auto statustxt = _u8L("Searching for optimal orientation"); + ctl.update_status(0, statustxt); + auto params = sla::RotOptimizeParams{} .accuracy(m_accuracy) .print_config(&m_default_print_cfg) - .statucb([this, &prev_status](int s) + .statucb([this, &prev_status, &ctl, &statustxt](int s) { if (s > 0 && s < 100) - update_status(prev_status + s / m_selected_object_ids.size(), - _(L("Searching for optimal orientation"))); + ctl.update_status(prev_status + s / m_selected_object_ids.size(), + statustxt); - return !was_canceled(); + return !ctl.was_canceled(); }); @@ -71,16 +76,20 @@ void RotoptimizeJob::process() prev_status += 100 / m_selected_object_ids.size(); - if (was_canceled()) break; + if (ctl.was_canceled()) break; } - update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : - _(L("Orientation found."))); + ctl.update_status(100, ctl.was_canceled() ? + _u8L("Orientation search canceled.") : + _u8L("Orientation found.")); } -void RotoptimizeJob::finalize() +RotoptimizeJob::RotoptimizeJob() : m_plater{wxGetApp().plater()} { prepare(); } + +void RotoptimizeJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (was_canceled()) return; + if (canceled || eptr) + return; for (const ObjRot &objrot : m_selected_object_ids) { ModelObject *o = m_plater->model().objects[size_t(objrot.idx)]; @@ -111,10 +120,8 @@ void RotoptimizeJob::finalize() // m_plater->find_new_position(o->instances); } - if (!was_canceled()) + if (!canceled) m_plater->update(); - - Job::finalize(); } }} diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index cdb367f23a..71a28deb7c 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -1,16 +1,17 @@ #ifndef ROTOPTIMIZEJOB_HPP #define ROTOPTIMIZEJOB_HPP -#include "PlaterJob.hpp" +#include "Job.hpp" #include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/I18N.hpp" -namespace Slic3r { +namespace Slic3r { namespace GUI { -namespace GUI { +class Plater; -class RotoptimizeJob : public PlaterJob +class RotoptimizeJob : public Job { using FindFn = std::function; @@ -44,19 +45,16 @@ class RotoptimizeJob : public PlaterJob }; std::vector m_selected_object_ids; - -protected: - - void prepare() override; - void process() override; + Plater *m_plater; public: - RotoptimizeJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + void prepare(); + void process(Ctl &ctl) override; - void finalize() override; + RotoptimizeJob(); + + void finalize(bool canceled, std::exception_ptr &) override; static constexpr size_t get_methods_count() { return std::size(Methods); } diff --git a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp new file mode 100644 index 0000000000..7dbecff2ae --- /dev/null +++ b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp @@ -0,0 +1,114 @@ +#ifndef SLAIMPORTDIALOG_HPP +#define SLAIMPORTDIALOG_HPP + +#include "SLAImportJob.hpp" + +#include +#include +#include +#include +#include + +#include "libslic3r/AppConfig.hpp" +#include "slic3r/GUI/I18N.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" + +//#include "libslic3r/Model.hpp" +//#include "libslic3r/PresetBundle.hpp" + +namespace Slic3r { namespace GUI { + +class SLAImportDialog: public wxDialog, public SLAImportJobView { + wxFilePickerCtrl *m_filepicker; + wxComboBox *m_import_dropdown, *m_quality_dropdown; + +public: + SLAImportDialog(Plater *plater) + : wxDialog{plater, wxID_ANY, "Import SLA archive"} + { + auto szvert = new wxBoxSizer{wxVERTICAL}; + auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; + + m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, + from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), + "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", + wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); + szfilepck->Add(m_filepicker, 1); + szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); + + auto szchoices = new wxBoxSizer{wxHORIZONTAL}; + + static const std::vector inp_choices = { + _(L("Import model and profile")), + _(L("Import profile only")), + _(L("Import model only")) + }; + + m_import_dropdown = new wxComboBox( + this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, + inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + + szchoices->Add(m_import_dropdown); + szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5); + + static const std::vector qual_choices = { + _(L("Accurate")), + _(L("Balanced")), + _(L("Quick")) + }; + + m_quality_dropdown = new wxComboBox( + this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, + qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + szchoices->Add(m_quality_dropdown); + + m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { + if (get_selection() == Sel::profileOnly) + m_quality_dropdown->Disable(); + else m_quality_dropdown->Enable(); + }); + + szvert->Add(szchoices, 0, wxALL, 5); + szvert->AddStretchSpacer(1); + auto szbtn = new wxBoxSizer(wxHORIZONTAL); + szbtn->Add(new wxButton{this, wxID_CANCEL}); + szbtn->Add(new wxButton{this, wxID_OK}); + szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); + + SetSizerAndFit(szvert); + } + + Sel get_selection() const override + { + int sel = m_import_dropdown->GetSelection(); + return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); + } + + Vec2i get_marchsq_windowsize() const override + { + enum { Accurate, Balanced, Fast}; + + switch(m_quality_dropdown->GetSelection()) + { + case Fast: return {8, 8}; + case Balanced: return {4, 4}; + default: + case Accurate: + return {2, 2}; + } + } + + std::string get_path() const override + { + return m_filepicker->GetPath().ToUTF8().data(); + } +}; + +}} // namespace Slic3r::GUI + +#endif // SLAIMPORTDIALOG_HPP diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 0d42cec2d3..1bb8cdf6c6 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -3,7 +3,6 @@ #include "libslic3r/Format/SL1.hpp" #include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/NotificationManager.hpp" @@ -11,104 +10,10 @@ #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" -#include -#include -#include #include -#include namespace Slic3r { namespace GUI { -enum class Sel { modelAndProfile, profileOnly, modelOnly}; - -class ImportDlg: public wxDialog { - wxFilePickerCtrl *m_filepicker; - wxComboBox *m_import_dropdown, *m_quality_dropdown; - -public: - ImportDlg(Plater *plater) - : wxDialog{plater, wxID_ANY, "Import SLA archive"} - { - auto szvert = new wxBoxSizer{wxVERTICAL}; - auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; - - m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, - from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), - "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", - wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); - szfilepck->Add(m_filepicker, 1); - szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); - - auto szchoices = new wxBoxSizer{wxHORIZONTAL}; - - static const std::vector inp_choices = { - _(L("Import model and profile")), - _(L("Import profile only")), - _(L("Import model only")) - }; - - m_import_dropdown = new wxComboBox( - this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, - inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); - - szchoices->Add(m_import_dropdown); - szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5); - - static const std::vector qual_choices = { - _(L("Accurate")), - _(L("Balanced")), - _(L("Quick")) - }; - - m_quality_dropdown = new wxComboBox( - this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, - qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); - szchoices->Add(m_quality_dropdown); - - m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { - if (get_selection() == Sel::profileOnly) - m_quality_dropdown->Disable(); - else m_quality_dropdown->Enable(); - }); - - szvert->Add(szchoices, 0, wxALL, 5); - szvert->AddStretchSpacer(1); - auto szbtn = new wxBoxSizer(wxHORIZONTAL); - szbtn->Add(new wxButton{this, wxID_CANCEL}); - szbtn->Add(new wxButton{this, wxID_OK}); - szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); - - SetSizerAndFit(szvert); - } - - Sel get_selection() const - { - int sel = m_import_dropdown->GetSelection(); - return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); - } - - Vec2i get_marchsq_windowsize() const - { - enum { Accurate, Balanced, Fast}; - - switch(m_quality_dropdown->GetSelection()) - { - case Fast: return {8, 8}; - case Balanced: return {4, 4}; - default: - case Accurate: - return {2, 2}; - } - } - - wxString get_path() const - { - return m_filepicker->GetPath(); - } -}; - class SLAImportJob::priv { public: Plater *plater; @@ -122,23 +27,28 @@ public: std::string err; ConfigSubstitutions config_substitutions; - ImportDlg import_dlg; + const SLAImportJobView * import_dlg; - priv(Plater *plt) : plater{plt}, import_dlg{plt} {} + priv(Plater *plt, const SLAImportJobView *view) : plater{plt}, import_dlg{view} {} }; -SLAImportJob::SLAImportJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater}, p{std::make_unique(plater)} -{} +SLAImportJob::SLAImportJob(const SLAImportJobView *view) + : p{std::make_unique(wxGetApp().plater(), view)} +{ + prepare(); +} SLAImportJob::~SLAImportJob() = default; -void SLAImportJob::process() +void SLAImportJob::process(Ctl &ctl) { - auto progr = [this](int s) { + auto statustxt = _u8L("Importing SLA archive"); + ctl.update_status(0, statustxt); + + auto progr = [&ctl, &statustxt](int s) { if (s < 100) - update_status(int(s), _(L("Importing SLA archive"))); - return !was_canceled(); + ctl.update_status(int(s), statustxt); + return !ctl.was_canceled(); }; if (p->path.empty()) return; @@ -161,15 +71,15 @@ void SLAImportJob::process() p->err = ex.what(); } - update_status(100, was_canceled() ? _(L("Importing canceled.")) : - _(L("Importing done."))); + ctl.update_status(100, ctl.was_canceled() ? _u8L("Importing canceled.") : + _u8L("Importing done.")); } void SLAImportJob::reset() { p->sel = Sel::modelAndProfile; p->mesh = {}; - p->profile = m_plater->sla_print().full_print_config(); + p->profile = p->plater->sla_print().full_print_config(); p->win = {2, 2}; p->path.Clear(); } @@ -178,22 +88,19 @@ void SLAImportJob::prepare() { reset(); - if (p->import_dlg.ShowModal() == wxID_OK) { - auto path = p->import_dlg.get_path(); - auto nm = wxFileName(path); - p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); - p->sel = p->import_dlg.get_selection(); - p->win = p->import_dlg.get_marchsq_windowsize(); - p->config_substitutions.clear(); - } else { - p->path = ""; - } + auto path = p->import_dlg->get_path(); + auto nm = wxFileName(path); + p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); + p->sel = p->import_dlg->get_selection(); + p->win = p->import_dlg->get_marchsq_windowsize(); + p->config_substitutions.clear(); } -void SLAImportJob::finalize() +void SLAImportJob::finalize(bool canceled, std::exception_ptr &eptr) { // Ignore the arrange result if aborted. - if (was_canceled()) return; + if (canceled || eptr) + return; if (!p->err.empty()) { show_error(p->plater, p->err); @@ -204,7 +111,7 @@ void SLAImportJob::finalize() std::string name = wxFileName(p->path).GetName().ToUTF8().data(); if (p->profile.empty()) { - m_plater->get_notification_manager()->push_notification( + p->plater->get_notification_manager()->push_notification( NotificationType::CustomNotification, NotificationManager::NotificationLevel::WarningNotificationLevel, _L("The imported SLA archive did not contain any presets. " @@ -213,7 +120,7 @@ void SLAImportJob::finalize() if (p->sel != Sel::modelOnly) { if (p->profile.empty()) - p->profile = m_plater->sla_print().full_print_config(); + p->profile = p->plater->sla_print().full_print_config(); const ModelObjectPtrs& objects = p->plater->model().objects; for (auto object : objects) diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp index c2ca10ef69..b2aea8bf89 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp @@ -1,22 +1,37 @@ #ifndef SLAIMPORTJOB_HPP #define SLAIMPORTJOB_HPP -#include "PlaterJob.hpp" +#include "Job.hpp" + +#include "libslic3r/Point.hpp" namespace Slic3r { namespace GUI { -class SLAImportJob : public PlaterJob { +class SLAImportJobView { +public: + enum Sel { modelAndProfile, profileOnly, modelOnly}; + + virtual ~SLAImportJobView() = default; + + virtual Sel get_selection() const = 0; + virtual Vec2i get_marchsq_windowsize() const = 0; + virtual std::string get_path() const = 0; +}; + +class Plater; + +class SLAImportJob : public Job { class priv; std::unique_ptr p; - -protected: - void prepare() override; - void process() override; - void finalize() override; + using Sel = SLAImportJobView::Sel; public: - SLAImportJob(std::shared_ptr pri, Plater *plater); + void prepare(); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &) override; + + SLAImportJob(const SLAImportJobView *); ~SLAImportJob(); void reset(); diff --git a/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp new file mode 100644 index 0000000000..d400490137 --- /dev/null +++ b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp @@ -0,0 +1,123 @@ +#ifndef THREADSAFEQUEUE_HPP +#define THREADSAFEQUEUE_HPP + +#include +#include +#include +#include +#include + +namespace Slic3r { namespace GUI { + +// Helper structure for overloads of ThreadSafeQueueSPSC::consume_one() +// to block if the queue is empty. +struct BlockingWait +{ + // Timeout to wait for the arrival of new element into the queue. + unsigned timeout_ms = 0; + + // An optional atomic flag to set true if an incoming element gets + // consumed. The flag will be atomically set to true when popping the + // front of the queue. + std::atomic *pop_flag = nullptr; +}; + +// A thread safe queue for one producer and one consumer. +template class Container = std::deque, + class... ContainerArgs> +class ThreadSafeQueueSPSC +{ + std::queue> m_queue; + mutable std::mutex m_mutex; + std::condition_variable m_cond_var; +public: + + // Consume one element, block if the queue is empty. + template bool consume_one(const BlockingWait &blkw, Fn &&fn) + { + static_assert(!std::is_reference_v, ""); + static_assert(std::is_default_constructible_v, ""); + static_assert(std::is_move_assignable_v || std::is_copy_assignable_v, ""); + + T el; + { + std::unique_lock lk{m_mutex}; + + auto pred = [this]{ return !m_queue.empty(); }; + if (blkw.timeout_ms > 0) { + auto timeout = std::chrono::milliseconds(blkw.timeout_ms); + if (!m_cond_var.wait_for(lk, timeout, pred)) + return false; + } + else + m_cond_var.wait(lk, pred); + + if constexpr (std::is_move_assignable_v) + el = std::move(m_queue.front()); + else + el = m_queue.front(); + + m_queue.pop(); + + if (blkw.pop_flag) + // The optional flag is set before the lock us unlocked. + blkw.pop_flag->store(true); + } + + fn(el); + return true; + } + + // Consume one element, return true if consumed, false if queue was empty. + template bool consume_one(Fn &&fn) + { + T el; + { + std::unique_lock lk{m_mutex}; + if (!m_queue.empty()) { + if constexpr (std::is_move_assignable_v) + el = std::move(m_queue.front()); + else + el = m_queue.front(); + + m_queue.pop(); + } else + return false; + } + + fn(el); + + return true; + } + + // Push element into the queue. + template void push(TArgs&&...el) + { + std::lock_guard lk{m_mutex}; + m_queue.emplace(std::forward(el)...); + m_cond_var.notify_one(); + } + + bool empty() const + { + std::lock_guard lk{m_mutex}; + return m_queue.empty(); + } + + size_t size() const + { + std::lock_guard lk{m_mutex}; + return m_queue.size(); + } + + void clear() + { + std::lock_guard lk{m_mutex}; + while (!m_queue.empty()) m_queue.pop(); + } +}; + +}} // namespace Slic3r::GUI + +#endif // THREADSAFEQUEUE_HPP diff --git a/src/slic3r/GUI/Jobs/Worker.hpp b/src/slic3r/GUI/Jobs/Worker.hpp new file mode 100644 index 0000000000..0bc7bc0863 --- /dev/null +++ b/src/slic3r/GUI/Jobs/Worker.hpp @@ -0,0 +1,119 @@ +#ifndef PRUSALSICER_WORKER_HPP +#define PRUSALSICER_WORKER_HPP + +#include + +#include "Job.hpp" + +namespace Slic3r { namespace GUI { + +// An interface of a worker that runs jobs on a dedicated worker thread, one +// after the other. It is assumed that every method of this class is called +// from the same main thread. +class Worker { +public: + // Queue up a new job after the current one. This call does not block. + // Returns false if the job gets discarded. + virtual bool push(std::unique_ptr job) = 0; + + // Returns true if no job is running, the job queue is empty and no job + // message is left to be processed. This means that nothing is left to + // finalize or take care of in the main thread. + virtual bool is_idle() const = 0; + + // Ask the current job gracefully to cancel. This call is not blocking and + // the job may or may not cancel eventually, depending on its + // implementation. Note that it is not trivial to kill a thread forcefully + // and we don't need that. + virtual void cancel() = 0; + + // This method will delete the queued jobs and cancel the current one. + virtual void cancel_all() = 0; + + // Needs to be called continuously to process events (like status update + // or finalizing of jobs) in the main thread. This can be done e.g. in a + // wxIdle handler. + virtual void process_events() = 0; + + // Wait until the current job finishes. Timeout will only be considered + // if not zero. Returns false if timeout is reached but the job has not + // finished. + virtual bool wait_for_current_job(unsigned timeout_ms = 0) = 0; + + // Wait until the whole job queue finishes. Timeout will only be considered + // if not zero. Returns false only if timeout is reached but the worker has + // not reached the idle state. + virtual bool wait_for_idle(unsigned timeout_ms = 0) = 0; + + // The destructor shall properly close the worker thread. + virtual ~Worker() = default; +}; + +template constexpr bool IsProcessFn = std::is_invocable_v; +template constexpr bool IsFinishFn = std::is_invocable_v; + +// Helper function to use the worker with arbitrary functors. +template>, + class = std::enable_if_t> > +bool queue_job(Worker &w, ProcessFn fn, FinishFn finishfn) +{ + struct LambdaJob: Job { + ProcessFn fn; + FinishFn finishfn; + + LambdaJob(ProcessFn pfn, FinishFn ffn) + : fn{std::move(pfn)}, finishfn{std::move(ffn)} + {} + + void process(Ctl &ctl) override { fn(ctl); } + void finalize(bool canceled, std::exception_ptr &eptr) override + { + finishfn(canceled, eptr); + } + }; + + auto j = std::make_unique(std::move(fn), std::move(finishfn)); + return w.push(std::move(j)); +} + +template>> +bool queue_job(Worker &w, ProcessFn fn) +{ + return queue_job(w, std::move(fn), [](bool, std::exception_ptr &) {}); +} + +inline bool queue_job(Worker &w, std::unique_ptr j) +{ + return w.push(std::move(j)); +} + +// Replace the current job queue with a new job. The signature is the same +// as for queue_job(). This cancels all jobs and +// will not wait. The new job will begin after the queue cancels properly. +// Note that this can be called from the UI thread and will not block it if +// the jobs take longer to cancel. +template bool replace_job(Worker &w, Args&& ...args) +{ + w.cancel_all(); + return queue_job(w, std::forward(args)...); +} + +// Cancel the current job and wait for it to actually be stopped. +inline bool stop_current_job(Worker &w, unsigned timeout_ms = 0) +{ + w.cancel(); + return w.wait_for_current_job(timeout_ms); +} + +// Cancel all pending jobs including current one and wait until the worker +// becomes idle. +inline bool stop_queue(Worker &w, unsigned timeout_ms = 0) +{ + w.cancel_all(); + return w.wait_for_idle(timeout_ms); +} + +}} // namespace Slic3r::GUI + +#endif // WORKER_HPP diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index c330f7cc92..ba3f6675fd 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -223,7 +223,7 @@ void KBShortcutsDialog::fill_shortcuts() { "A", L("Horizontal slider - Move active thumb Left") }, { "D", L("Horizontal slider - Move active thumb Right") }, { "X", L("On/Off one layer mode of the vertical slider") }, - { "L", L("Show/Hide Legend and Estimated printing time") }, + { "L", L("Show/Hide legend") }, { "C", L("Show/Hide G-code window") }, }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index fe96136e6c..dd6f45318f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -47,6 +47,7 @@ #include "GUI_ObjectList.hpp" #include "GalleryDialog.hpp" #include "NotificationManager.hpp" +#include "Preferences.hpp" #ifdef _WIN32 #include @@ -272,6 +273,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S if (m_plater != nullptr) { m_plater->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); m_plater->show_action_buttons(true); + + preferences_dialog = new PreferencesDialog(this); } } @@ -571,7 +574,7 @@ void MainFrame::shutdown() #endif // _WIN32 if (m_plater != nullptr) { - m_plater->stop_jobs(); + m_plater->get_ui_job_worker().cancel_all(); // Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC, // when closing the application using Command+Q, a mouse event is triggered after this lambda is completed, @@ -1208,7 +1211,7 @@ void MainFrame::init_menubar_as_editor() append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S Archive") + dots, _L("Load an SL1 / Sl1S archive"), [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, - [this](){return m_plater != nullptr && !m_plater->is_any_job_running(); }, this); + [this](){return m_plater != nullptr && m_plater->get_ui_job_worker().is_idle(); }, this); import_menu->AppendSeparator(); append_menu_item(import_menu, wxID_ANY, _L("Import &Config") + dots + "\tCtrl+L", _L("Load exported configuration file"), @@ -1427,6 +1430,11 @@ void MainFrame::init_menubar_as_editor() append_menu_check_item(viewMenu, wxID_ANY, _L("Show &Labels") + sep + "E", _L("Show object/instance labels in 3D scene"), [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this); +#if ENABLE_PREVIEW_LAYOUT + append_menu_check_item(viewMenu, wxID_ANY, _L("Show Legen&d") + sep + "L", _L("Show legend in preview"), + [this](wxCommandEvent&) { m_plater->show_legend(!m_plater->is_legend_shown()); }, this, + [this]() { return m_plater->is_preview_shown(); }, [this]() { return m_plater->is_legend_shown(); }, this); +#endif // ENABLE_PREVIEW_LAYOUT append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse Sidebar") + sep + "Shift+" + sep_space + "Tab", _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); @@ -1544,6 +1552,12 @@ void MainFrame::init_menubar_as_gcodeviewer() if (m_plater != nullptr) { viewMenu = new wxMenu(); add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this)); +#if ENABLE_PREVIEW_LAYOUT + viewMenu->AppendSeparator(); + append_menu_check_item(viewMenu, wxID_ANY, _L("Show legen&d") + sep + "L", _L("Show legend"), + [this](wxCommandEvent&) { m_plater->show_legend(!m_plater->is_legend_shown()); }, this, + [this]() { return m_plater->is_preview_shown(); }, [this]() { return m_plater->is_legend_shown(); }, this); +#endif // ENABLE_PREVIEW_LAYOUT } // helpmenu diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 951ed70a1d..f385ee8f85 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -32,6 +32,7 @@ class Tab; class PrintHostQueueDialog; class Plater; class MainFrame; +class PreferencesDialog; enum QuickSlice { @@ -203,6 +204,7 @@ public: DiffPresetDialog diff_dialog; wxWindow* m_plater_page{ nullptr }; // wxProgressDialog* m_progress_dialog { nullptr }; + PreferencesDialog* preferences_dialog { nullptr }; PrintHostQueueDialog* m_printhost_queue_dlg; // std::shared_ptr m_statusbar; diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 4b0a9d8639..a2d65a73c0 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -14,6 +14,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "format.hpp" #include "I18N.hpp" @@ -137,9 +138,9 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); wxFont monospace = wxGetApp().code_font(); wxColour text_clr = wxGetApp().get_label_clr_default(); - wxColour bgr_clr = parent->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + wxColour bgr_clr = parent->GetBackgroundColour(); + auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); const int font_size = font.GetPointSize(); int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size }; html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size); diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 67c3fbdbde..29d8baaa63 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -74,6 +74,12 @@ public: label(_(label)), label_tooltip(_(tooltip)) {} Line() : m_is_separator(true) {} + Line(const std::string& opt_key, const wxString& label, const wxString& tooltip) : + label(_(label)), label_tooltip(_(tooltip)) + { + m_options.push_back(Option({ opt_key, coNone }, opt_key)); + } + bool is_separator() const { return m_is_separator; } const std::vector& get_extra_widgets() const {return m_extra_widgets;} @@ -180,6 +186,8 @@ public: // if we have to set the same control alignment for different option groups, // we have to set same max contrtol width to all of them void set_max_win_width(int max_win_width); + void set_use_custom_ctrl(bool use_custom_ctrl) { m_use_custom_ctrl = use_custom_ctrl; } + const std::map& get_optioms_map() { return m_options; } bool is_activated() { return sizer != nullptr; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2a4ba6f48a..21aa5fc124 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -73,7 +73,10 @@ #include "Jobs/FillBedJob.hpp" #include "Jobs/RotoptimizeJob.hpp" #include "Jobs/SLAImportJob.hpp" +#include "Jobs/SLAImportDialog.hpp" #include "Jobs/NotificationProgressIndicator.hpp" +#include "Jobs/PlaterWorker.hpp" +#include "Jobs/BoostThreadWorker.hpp" #include "BackgroundSlicingProcess.hpp" #include "PrintHostDialogs.hpp" #include "ConfigWizard.hpp" @@ -1185,10 +1188,10 @@ void Sidebar::jump_to_option(const std::string& opt_key, Preset::Type type, cons void Sidebar::jump_to_option(size_t selected) { const Search::Option& opt = p->searcher.get_option(selected); - wxGetApp().get_tab(opt.type)->activate_option(opt.opt_key(), boost::nowide::narrow(opt.category)); - - // Switch to the Settings NotePad -// wxGetApp().mainframe->select_tab(); + if (opt.type == Preset::TYPE_PREFERENCES) + wxGetApp().open_preferences(opt.opt_key(), boost::nowide::narrow(opt.group)); + else + wxGetApp().get_tab(opt.type)->activate_option(opt.opt_key(), boost::nowide::narrow(opt.category)); } ObjectManipulation* Sidebar::obj_manipul() @@ -1638,54 +1641,12 @@ struct Plater::priv BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; - // Jobs defined inside the group class will be managed so that only one can - // run at a time. Also, the background process will be stopped if a job is - // started. It is up the the plater to ensure that the background slicing - // can't be restarted while a ui job is still running. - class Jobs: public ExclusiveJobGroup - { - priv *m; - size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id; - std::shared_ptr m_pri; - - void before_start() override { m->background_process.stop(); } - - public: - Jobs(priv *_m) : - m(_m), - m_pri{std::make_shared(m->notification_manager.get())} - { - m_arrange_id = add_job(std::make_unique(m_pri, m->q)); - m_fill_bed_id = add_job(std::make_unique(m_pri, m->q)); - m_rotoptimize_id = add_job(std::make_unique(m_pri, m->q)); - m_sla_import_id = add_job(std::make_unique(m_pri, m->q)); - } - - void arrange() - { - m->take_snapshot(_L("Arrange")); - start(m_arrange_id); - } - - void fill_bed() - { - m->take_snapshot(_L("Fill bed")); - start(m_fill_bed_id); - } - - void optimize_rotation() - { - m->take_snapshot(_L("Optimize Rotation")); - start(m_rotoptimize_id); - } - - void import_sla_arch() - { - m->take_snapshot(_L("Import SLA archive")); - start(m_sla_import_id); - } - - } m_ui_jobs; + // TODO: A mechanism would be useful for blocking the plater interactions: + // objects would be frozen for the user. In case of arrange, an animation + // could be shown, or with the optimize orientations, partial results + // could be displayed. + PlaterWorker m_worker; + SLAImportDialog * m_sla_import_dlg; bool delayed_scene_refresh; std::string delayed_error_message; @@ -1778,6 +1739,11 @@ struct Plater::priv bool are_view3D_labels_shown() const { return (current_panel == view3D) && view3D->get_canvas3d()->are_labels_shown(); } void show_view3D_labels(bool show) { if (current_panel == view3D) view3D->get_canvas3d()->show_labels(show); } +#if ENABLE_PREVIEW_LAYOUT + bool is_legend_shown() const { return (current_panel == preview) && preview->get_canvas3d()->is_legend_shown(); } + void show_legend(bool show) { if (current_panel == preview) preview->get_canvas3d()->show_legend(show); } +#endif // ENABLE_PREVIEW_LAYOUT + bool is_sidebar_collapsed() const { return sidebar->is_collapsed(); } void collapse_sidebar(bool collapse); @@ -1791,7 +1757,9 @@ struct Plater::priv bool init_view_toolbar(); bool init_collapse_toolbar(); +#if !ENABLE_PREVIEW_LAYOUT void update_preview_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT void update_preview_moves_slider(); void enable_preview_moves_slider(bool enable); @@ -2013,7 +1981,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , notification_manager(std::make_unique(q)) - , m_ui_jobs(this) + , m_worker{q, std::make_unique(notification_manager.get()), "ui_worker"} + , m_sla_import_dlg{new SLAImportDialog{q}} , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") , collapse_toolbar(GLToolbar::Normal, "Collapse") @@ -2949,7 +2918,7 @@ void Plater::priv::remove(size_t obj_idx) if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); - m_ui_jobs.cancel_all(); + m_worker.cancel_all(); model.delete_object(obj_idx); update(); // Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model. @@ -2964,7 +2933,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) if (! model.objects[obj_idx]->name.empty()) snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); Plater::TakeSnapshot snapshot(q, snapshot_label); - m_ui_jobs.cancel_all(); + m_worker.cancel_all(); model.delete_object(obj_idx); update(); object_list_changed(); @@ -2982,7 +2951,7 @@ void Plater::priv::delete_all_objects_from_model() view3D->get_canvas3d()->reset_sequential_print_clearance(); - m_ui_jobs.cancel_all(); + m_worker.cancel_all(); // Stop and reset the Print content. background_process.reset(); @@ -3014,7 +2983,7 @@ void Plater::priv::reset() view3D->get_canvas3d()->reset_sequential_print_clearance(); - m_ui_jobs.cancel_all(); + m_worker.cancel_all(); // Stop and reset the Print content. this->background_process.reset(); @@ -3311,7 +3280,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. bool Plater::priv::restart_background_process(unsigned int state) { - if (m_ui_jobs.is_any_running()) { + if (!m_worker.is_idle()) { // Avoid a race condition return false; } @@ -3972,7 +3941,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) { if (evt.status.percent >= -1) { - if (m_ui_jobs.is_any_running()) { + if (!m_worker.is_idle()) { // Avoid a race condition return; } @@ -4499,10 +4468,12 @@ bool Plater::priv::init_collapse_toolbar() return true; } +#if !ENABLE_PREVIEW_LAYOUT void Plater::priv::update_preview_bottom_toolbar() { preview->update_bottom_toolbar(); } +#endif // !ENABLE_PREVIEW_LAYOUT void Plater::priv::update_preview_moves_slider() { @@ -4659,7 +4630,7 @@ bool Plater::priv::can_simplify() const bool Plater::priv::can_increase_instances() const { - if (m_ui_jobs.is_any_running() + if (!m_worker.is_idle() || q->canvas3D()->get_gizmos_manager().is_in_editing_mode()) return false; @@ -4669,7 +4640,7 @@ bool Plater::priv::can_increase_instances() const bool Plater::priv::can_decrease_instances() const { - if (m_ui_jobs.is_any_running() + if (!m_worker.is_idle() || q->canvas3D()->get_gizmos_manager().is_in_editing_mode()) return false; @@ -4689,7 +4660,7 @@ bool Plater::priv::can_split_to_volumes() const bool Plater::priv::can_arrange() const { - return !model.objects.empty() && !m_ui_jobs.is_any_running(); + return !model.objects.empty() && m_worker.is_idle(); } bool Plater::priv::can_layers_editing() const @@ -5143,8 +5114,11 @@ void Plater::add_model(bool imperial_units/* = false*/) void Plater::import_sl1_archive() { - if (!p->m_ui_jobs.is_any_running()) - p->m_ui_jobs.import_sla_arch(); + auto &w = get_ui_job_worker(); + if (w.is_idle() && p->m_sla_import_dlg->ShowModal() == wxID_OK) { + p->take_snapshot(_L("Import SLA archive")); + replace_job(w, std::make_unique(p->m_sla_import_dlg)); + } } void Plater::extract_config_from_project() @@ -5426,12 +5400,9 @@ bool Plater::load_files(const wxArrayString& filenames) void Plater::update() { p->update(); } -void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); } +Worker &Plater::get_ui_job_worker() { return p->m_worker; } -bool Plater::is_any_job_running() const -{ - return p->m_ui_jobs.is_any_running(); -} +const Worker &Plater::get_ui_job_worker() const { return p->m_worker; } void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } @@ -5446,6 +5417,11 @@ bool Plater::is_view3D_shown() const { return p->is_view3D_shown(); } bool Plater::are_view3D_labels_shown() const { return p->are_view3D_labels_shown(); } void Plater::show_view3D_labels(bool show) { p->show_view3D_labels(show); } +#if ENABLE_PREVIEW_LAYOUT +bool Plater::is_legend_shown() const { return p->is_legend_shown(); } +void Plater::show_legend(bool show) { p->show_legend(show); } +#endif // ENABLE_PREVIEW_LAYOUT + bool Plater::is_sidebar_collapsed() const { return p->is_sidebar_collapsed(); } void Plater::collapse_sidebar(bool show) { p->collapse_sidebar(show); } @@ -5472,7 +5448,7 @@ void Plater::remove_selected() return; Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects")); - p->m_ui_jobs.cancel_all(); + get_ui_job_worker().cancel_all(); p->view3D->delete_selected(); } @@ -5581,8 +5557,11 @@ void Plater::set_number_of_copies(/*size_t num*/) void Plater::fill_bed_with_instances() { - if (!p->m_ui_jobs.is_any_running()) - p->m_ui_jobs.fill_bed(); + auto &w = get_ui_job_worker(); + if (w.is_idle()) { + p->take_snapshot(_L("Fill bed")); + replace_job(w, std::make_unique()); + } } bool Plater::is_selection_empty() const @@ -5757,23 +5736,26 @@ void Plater::export_stl(bool extended, bool selection_only) return; // Following lambda generates a combined mesh for export with normals pointing outwards. - auto mesh_to_export = [](const ModelObject* mo, bool instances) -> TriangleMesh { + auto mesh_to_export = [](const ModelObject& mo, int instance_id) { TriangleMesh mesh; - for (const ModelVolume *v : mo->volumes) + for (const ModelVolume* v : mo.volumes) if (v->is_model_part()) { TriangleMesh vol_mesh(v->mesh()); vol_mesh.transform(v->get_matrix(), true); mesh.merge(vol_mesh); } - if (instances) { + if (instance_id == -1) { TriangleMesh vols_mesh(mesh); mesh = TriangleMesh(); - for (const ModelInstance *i : mo->instances) { + for (const ModelInstance* i : mo.instances) { TriangleMesh m = vols_mesh; m.transform(i->get_matrix(), true); mesh.merge(m); } } + else if (0 <= instance_id && instance_id < int(mo.instances.size())) + mesh.transform(mo.instances[instance_id]->get_matrix(), true); + return mesh; }; @@ -5782,7 +5764,7 @@ void Plater::export_stl(bool extended, bool selection_only) if (selection_only) { const ModelObject* model_object = p->model.objects[obj_idx]; if (selection.get_mode() == Selection::Instance) - mesh = mesh_to_export(model_object, selection.is_single_full_object() && model_object->instances.size() > 1); + mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx()); else { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); mesh = model_object->volumes[volume->volume_idx()]->mesh(); @@ -5794,7 +5776,7 @@ void Plater::export_stl(bool extended, bool selection_only) } else { for (const ModelObject* o : p->model.objects) { - mesh.merge(mesh_to_export(o, true)); + mesh.merge(mesh_to_export(*o, -1)); } } } @@ -5810,18 +5792,18 @@ void Plater::export_stl(bool extended, bool selection_only) if (model_object->id() != p->model.objects[obj_idx]->id()) continue; } - Transform3d mesh_trafo_inv = object->trafo().inverse(); - bool is_left_handed = object->is_left_handed(); + const Transform3d mesh_trafo_inv = object->trafo().inverse(); + const bool is_left_handed = object->is_left_handed(); TriangleMesh pad_mesh; - bool has_pad_mesh = extended && object->has_mesh(slaposPad); + const bool has_pad_mesh = extended && object->has_mesh(slaposPad); if (has_pad_mesh) { pad_mesh = object->get_mesh(slaposPad); pad_mesh.transform(mesh_trafo_inv); } TriangleMesh supports_mesh; - bool has_supports_mesh = extended && object->has_mesh(slaposSupportTree); + const bool has_supports_mesh = extended && object->has_mesh(slaposSupportTree); if (has_supports_mesh) { supports_mesh = object->get_mesh(slaposSupportTree); supports_mesh.transform(mesh_trafo_inv); @@ -5833,9 +5815,9 @@ void Plater::export_stl(bool extended, bool selection_only) assert(it != model_object->instances.end()); if (it != model_object->instances.end()) { - bool one_inst_only = selection_only && ! selection.is_single_full_object(); + const bool one_inst_only = selection_only && ! selection.is_single_full_object(); - int instance_idx = it - model_object->instances.begin(); + const int instance_idx = it - model_object->instances.begin(); const Transform3d& inst_transform = one_inst_only ? Transform3d::Identity() : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); @@ -5982,8 +5964,14 @@ void Plater::reslice() if (canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) return; - // Stop arrange and (or) optimize rotation tasks. - this->stop_jobs(); + // Stop the running (and queued) UI jobs and only proceed if they actually + // get stopped. + unsigned timeout_ms = 10000; + if (!stop_queue(this->get_ui_job_worker(), timeout_ms)) { + BOOST_LOG_TRIVIAL(error) << "Could not stop UI job within " + << timeout_ms << " milliseconds timeout!"; + return; + } if (printer_technology() == ptSLA) { for (auto& object : model().objects) @@ -6447,8 +6435,11 @@ GLCanvas3D* Plater::get_current_canvas3D() void Plater::arrange() { - if (!p->m_ui_jobs.is_any_running()) - p->m_ui_jobs.arrange(); + auto &w = get_ui_job_worker(); + if (w.is_idle()) { + p->take_snapshot(_L("Arrange")); + replace_job(w, std::make_unique()); + } } void Plater::set_current_canvas_as_dirty() @@ -6619,7 +6610,6 @@ void Plater::suppress_background_process(const bool stop_background_process) void Plater::mirror(Axis axis) { p->mirror(axis); } void Plater::split_object() { p->split_object(); } void Plater::split_volume() { p->split_volume(); } -void Plater::optimize_rotation() { if (!p->m_ui_jobs.is_any_running()) p->m_ui_jobs.optimize_rotation(); } void Plater::update_menus() { p->menus.update(); } void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); } @@ -6760,10 +6750,12 @@ GLToolbar& Plater::get_collapse_toolbar() return p->collapse_toolbar; } +#if !ENABLE_PREVIEW_LAYOUT void Plater::update_preview_bottom_toolbar() { p->update_preview_bottom_toolbar(); } +#endif // !ENABLE_PREVIEW_LAYOUT void Plater::update_preview_moves_slider() { @@ -6873,6 +6865,12 @@ bool Plater::is_render_statistic_dialog_visible() const return p->show_render_statistic_dialog; } +#if ENABLE_PREVIEW_LAYOUT +void Plater::set_keep_current_preview_type(bool value) +{ + p->preview->set_keep_current_preview_type(value); +} +#endif // ENABLE_PREVIEW_LAYOUT Plater::TakeSnapshot::TakeSnapshot(Plater *plater, const std::string &snapshot_name) : TakeSnapshot(plater, from_u8(snapshot_name)) {} diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 3b4236a615..0b26f09e11 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -14,6 +14,7 @@ #include "libslic3r/BoundingBox.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "Jobs/Job.hpp" +#include "Jobs/Worker.hpp" #include "Search.hpp" class wxButton; @@ -177,8 +178,41 @@ public: const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; } void update(); - void stop_jobs(); - bool is_any_job_running() const; + + // Get the worker handling the UI jobs (arrange, fill bed, etc...) + // Here is an example of starting up an ad-hoc job: + // queue_job( + // get_ui_job_worker(), + // [](Job::Ctl &ctl) { + // // Executed in the worker thread + // + // CursorSetterRAII cursor_setter{ctl}; + // std::string msg = "Running"; + // + // ctl.update_status(0, msg); + // for (int i = 0; i < 100; i++) { + // usleep(100000); + // if (ctl.was_canceled()) break; + // ctl.update_status(i + 1, msg); + // } + // ctl.update_status(100, msg); + // }, + // [](bool, std::exception_ptr &e) { + // // Executed in UI thread after the work is done + // + // try { + // if (e) std::rethrow_exception(e); + // } catch (std::exception &e) { + // BOOST_LOG_TRIVIAL(error) << e.what(); + // } + // e = nullptr; + // }); + // This would result in quick run of the progress indicator notification + // from 0 to 100. Use replace_job() instead of queue_job() to cancel all + // pending jobs. + Worker& get_ui_job_worker(); + const Worker & get_ui_job_worker() const; + void select_view(const std::string& direction); void select_view_3D(const std::string& name); @@ -189,6 +223,11 @@ public: bool are_view3D_labels_shown() const; void show_view3D_labels(bool show); +#if ENABLE_PREVIEW_LAYOUT + bool is_legend_shown() const; + void show_legend(bool show); +#endif // ENABLE_PREVIEW_LAYOUT + bool is_sidebar_collapsed() const; void collapse_sidebar(bool show); @@ -302,7 +341,6 @@ public: void mirror(Axis axis); void split_object(); void split_volume(); - void optimize_rotation(); bool can_delete() const; bool can_delete_all() const; @@ -349,7 +387,9 @@ public: const GLToolbar& get_collapse_toolbar() const; GLToolbar& get_collapse_toolbar(); +#if !ENABLE_PREVIEW_LAYOUT void update_preview_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT void update_preview_moves_slider(); void enable_preview_moves_slider(bool enable); @@ -415,6 +455,10 @@ public: void toggle_render_statistic_dialog(); bool is_render_statistic_dialog_visible() const; +#if ENABLE_PREVIEW_LAYOUT + void set_keep_current_preview_type(bool value); +#endif // ENABLE_PREVIEW_LAYOUT + // Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu. bool PopupMenu(wxMenu *menu, const wxPoint& pos = wxDefaultPosition); bool PopupMenu(wxMenu *menu, int x, int y) { return this->PopupMenu(menu, wxPoint(x, y)); } diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 996d6b2c2e..c35c54fcec 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -42,22 +42,37 @@ namespace Slic3r { namespace GUI { -PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab, const std::string& highlight_opt_key) : +PreferencesDialog::PreferencesDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) { #ifdef __WXOSX__ isOSX = true; #endif - build(selected_tab); + build(); + + m_highlighter.set_timer_owner(this, 0); +} + +void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::string()*/, const std::string& tab_name/*= std::string()*/) +{ + int selected_tab = 0; + for ( ; selected_tab < int(tabs->GetPageCount()); selected_tab++) + if (tabs->GetPageText(selected_tab) == _(tab_name)) + break; + if (selected_tab < tabs->GetPageCount()) + tabs->SetSelection(selected_tab); + if (!highlight_opt_key.empty()) init_highlighter(highlight_opt_key); + + this->ShowModal(); } static std::shared_ptrcreate_options_tab(const wxString& title, wxBookCtrlBase* tabs) { wxPanel* tab = new wxPanel(tabs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); - tabs->AddPage(tab, title); + tabs->AddPage(tab, _(title)); tab->SetFont(wxGetApp().normal_font()); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); @@ -66,6 +81,7 @@ static std::shared_ptrcreate_options_tab(const wxString& tit std::shared_ptr optgroup = std::make_shared(tab); optgroup->label_width = 40; + optgroup->set_config_category_and_type(title, int(Preset::TYPE_PREFERENCES)); return optgroup; } @@ -75,9 +91,67 @@ static void activate_options_tab(std::shared_ptr optgroup) optgroup->update_visibility(comSimple); wxBoxSizer* sizer = static_cast(static_cast(optgroup->parent())->GetSizer()); sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); + + // apply sercher + wxGetApp().sidebar().get_searcher().append_preferences_options(optgroup->get_lines()); } -void PreferencesDialog::build(size_t selected_tab) +static void append_bool_option( std::shared_ptr optgroup, + const std::string& opt_key, + const std::string& label, + const std::string& tooltip, + bool def_val, + ConfigOptionMode mode = comSimple) +{ + ConfigOptionDef def = {opt_key, coBool}; + def.label = label; + def.tooltip = tooltip; + def.mode = mode; + def.set_default_value(new ConfigOptionBool{ def_val }); + Option option(def, opt_key); + optgroup->append_single_option_line(option); + + // fill data to the Search Dialog + wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); +} + +static void append_enum_option( std::shared_ptr optgroup, + const std::string& opt_key, + const std::string& label, + const std::string& tooltip, + const ConfigOption* def_val, + const t_config_enum_values *enum_keys_map, + std::initializer_list enum_values, + std::initializer_list enum_labels, + ConfigOptionMode mode = comSimple) +{ + ConfigOptionDef def = {opt_key, coEnum }; + def.label = label; + def.tooltip = tooltip; + def.mode = mode; + def.enum_keys_map = enum_keys_map; + def.enum_values = std::vector(enum_values); + def.enum_labels = std::vector(enum_labels); + + def.set_default_value(def_val); + Option option(def, opt_key); + optgroup->append_single_option_line(option); + + // fill data to the Search Dialog + wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); +} + +static void append_preferences_option_to_searcer(std::shared_ptr optgroup, + const std::string& opt_key, + const wxString& label) +{ + // fill data to the Search Dialog + wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); + // apply sercher + wxGetApp().sidebar().get_searcher().append_preferences_option(Line(opt_key, label, "")); +} + +void PreferencesDialog::build() { #ifdef _WIN32 wxGetApp().UpdateDarkUI(this); @@ -90,20 +164,14 @@ void PreferencesDialog::build(size_t selected_tab) auto app_config = get_app_config(); #ifdef _MSW_DARK_MODE - wxBookCtrlBase* tabs; -// if (wxGetApp().dark_mode()) - tabs = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT); -/* else { - tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT); - tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - }*/ + tabs = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT); #else - wxNotebook* tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL |wxNB_NOPAGETHEME | wxNB_DEFAULT ); + tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL |wxNB_NOPAGETHEME | wxNB_DEFAULT ); tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif // Add "General" tab - m_optgroup_general = create_options_tab(_L("General"), tabs); + m_optgroup_general = create_options_tab(L("General"), tabs); m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project") m_values[opt_key] = boost::any_cast(value) ? "none" : "discard"; @@ -115,233 +183,181 @@ void PreferencesDialog::build(size_t selected_tab) bool is_editor = wxGetApp().is_editor(); - ConfigOptionDef def; - Option option(def, ""); if (is_editor) { - def.label = L("Remember output directory"); - def.type = coBool; - def.tooltip = L("If this is enabled, Slic3r will prompt the last output directory " - "instead of the one containing the input files."); - def.set_default_value(new ConfigOptionBool{ app_config->has("remember_output_path") ? app_config->get("remember_output_path") == "1" : true }); - option = Option(def, "remember_output_path"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "remember_output_path", + L("Remember output directory"), + L("If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files."), + app_config->has("remember_output_path") ? app_config->get("remember_output_path") == "1" : true); - def.label = L("Auto-center parts"); - def.type = coBool; - def.tooltip = L("If this is enabled, Slic3r will auto-center objects " - "around the print bed center."); - def.set_default_value(new ConfigOptionBool{ app_config->get("autocenter") == "1" }); - option = Option(def, "autocenter"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "autocenter", + L("Auto-center parts"), + L("If this is enabled, Slic3r will auto-center objects around the print bed center."), + app_config->get("autocenter") == "1"); - def.label = L("Background processing"); - def.type = coBool; - def.tooltip = L("If this is enabled, Slic3r will pre-process objects as soon " - "as they\'re loaded in order to save time when exporting G-code."); - def.set_default_value(new ConfigOptionBool{ app_config->get("background_processing") == "1" }); - option = Option(def, "background_processing"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "background_processing", + L("Background processing"), + L("If this is enabled, Slic3r will pre-process objects as soon " + "as they\'re loaded in order to save time when exporting G-code."), + app_config->get("background_processing") == "1"); m_optgroup_general->append_separator(); // Please keep in sync with ConfigWizard - def.label = L("Export sources full pathnames to 3mf and amf"); - def.type = coBool; - def.tooltip = L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked."); - def.set_default_value(new ConfigOptionBool(app_config->get("export_sources_full_pathnames") == "1")); - option = Option(def, "export_sources_full_pathnames"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "export_sources_full_pathnames", + L("Export sources full pathnames to 3mf and amf"), + L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked."), + app_config->get("export_sources_full_pathnames") == "1"); #ifdef _WIN32 // Please keep in sync with ConfigWizard - def.label = L("Associate .3mf files to PrusaSlicer"); - def.type = coBool; - def.tooltip = L("If enabled, sets PrusaSlicer as default application to open .3mf files."); - def.set_default_value(new ConfigOptionBool(app_config->get("associate_3mf") == "1")); - option = Option(def, "associate_3mf"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "associate_3mf", + L("Associate .3mf files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .3mf files."), + app_config->get("associate_3mf") == "1"); - def.label = L("Associate .stl files to PrusaSlicer"); - def.type = coBool; - def.tooltip = L("If enabled, sets PrusaSlicer as default application to open .stl files."); - def.set_default_value(new ConfigOptionBool(app_config->get("associate_stl") == "1")); - option = Option(def, "associate_stl"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "associate_stl", + L("Associate .stl files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .stl files."), + app_config->get("associate_stl") == "1"); #endif // _WIN32 m_optgroup_general->append_separator(); // Please keep in sync with ConfigWizard - def.label = L("Update built-in Presets automatically"); - def.type = coBool; - def.tooltip = L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup."); - def.set_default_value(new ConfigOptionBool(app_config->get("preset_update") == "1")); - option = Option(def, "preset_update"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "preset_update", + L("Update built-in Presets automatically"), + L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded " + "into a separate temporary location. When a new preset version becomes available it is offered at application startup."), + app_config->get("preset_update") == "1"); - def.label = L("Suppress \" - default - \" presets"); - def.type = coBool; - def.tooltip = L("Suppress \" - default - \" presets in the Print / Filament / Printer " - "selections once there are any other valid presets available."); - def.set_default_value(new ConfigOptionBool{ app_config->get("no_defaults") == "1" }); - option = Option(def, "no_defaults"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "no_defaults", + L("Suppress \" - default - \" presets"), + L("Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available."), + app_config->get("no_defaults") == "1"); - def.label = L("Show incompatible print and filament presets"); - def.type = coBool; - def.tooltip = L("When checked, the print and filament presets are shown in the preset editor " - "even if they are marked as incompatible with the active printer"); - def.set_default_value(new ConfigOptionBool{ app_config->get("show_incompatible_presets") == "1" }); - option = Option(def, "show_incompatible_presets"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "show_incompatible_presets", + L("Show incompatible print and filament presets"), + L("When checked, the print and filament presets are shown in the preset editor " + "even if they are marked as incompatible with the active printer"), + app_config->get("show_incompatible_presets") == "1"); m_optgroup_general->append_separator(); - def.label = L("Show drop project dialog"); - def.type = coBool; - def.tooltip = L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load."); - def.set_default_value(new ConfigOptionBool{ app_config->get("show_drop_project_dialog") == "1" }); - option = Option(def, "show_drop_project_dialog"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "show_drop_project_dialog", + L("Show drop project dialog"), + L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load."), + app_config->get("show_drop_project_dialog") == "1"); + append_bool_option(m_optgroup_general, "single_instance", #if __APPLE__ - def.label = L("Allow just a single PrusaSlicer instance"); - def.type = coBool; - def.tooltip = L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances of same app from the command line. In such case this settings will allow only one instance."); + L("Allow just a single PrusaSlicer instance"), + L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances " + "of same app from the command line. In such case this settings will allow only one instance."), #else - def.label = L("Allow just a single PrusaSlicer instance"); - def.type = coBool; - def.tooltip = L("If this is enabled, when starting PrusaSlicer and another instance of the same PrusaSlicer is already running, that instance will be reactivated instead."); + L("Allow just a single PrusaSlicer instance"), + L("If this is enabled, when starting PrusaSlicer and another instance of the same PrusaSlicer is already running, that instance will be reactivated instead."), #endif - def.set_default_value(new ConfigOptionBool{ app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false }); - option = Option(def, "single_instance"); - m_optgroup_general->append_single_option_line(option); + app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false ); m_optgroup_general->append_separator(); - def.label = L("Ask for unsaved changes in project"); - def.type = coBool; - def.tooltip = L("Always ask for unsaved changes in project, when: \n" + append_bool_option(m_optgroup_general, "default_action_on_dirty_project", + L("Ask for unsaved changes in project"), + L("Always ask for unsaved changes in project, when: \n" "- Closing PrusaSlicer,\n" - "- Loading or creating a new project"); - def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_dirty_project").empty() }); - option = Option(def, "default_action_on_dirty_project"); - m_optgroup_general->append_single_option_line(option); + "- Loading or creating a new project"), + app_config->get("default_action_on_dirty_project").empty()); m_optgroup_general->append_separator(); - def.label = L("Ask to save unsaved changes in presets when closing the application or when loading a new project"); - def.type = coBool; - def.tooltip = L("Always ask for unsaved changes in presets, when: \n" + append_bool_option(m_optgroup_general, "default_action_on_close_application", + L("Ask to save unsaved changes in presets when closing the application or when loading a new project"), + L("Always ask for unsaved changes in presets, when: \n" "- Closing PrusaSlicer while some presets are modified,\n" - "- Loading a new project while some presets are modified"); - def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_close_application") == "none" }); - option = Option(def, "default_action_on_close_application"); - m_optgroup_general->append_single_option_line(option); + "- Loading a new project while some presets are modified"), + app_config->get("default_action_on_close_application") == "none"); - def.label = L("Ask for unsaved changes in presets when selecting new preset"); - def.type = coBool; - def.tooltip = L("Always ask for unsaved changes in presets when selecting new preset or resetting a preset"); - def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_select_preset") == "none" }); - option = Option(def, "default_action_on_select_preset"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "default_action_on_select_preset", + L("Ask for unsaved changes in presets when selecting new preset"), + L("Always ask for unsaved changes in presets when selecting new preset or resetting a preset"), + app_config->get("default_action_on_select_preset") == "none"); - def.label = L("Ask for unsaved changes in presets when creating new project"); - def.type = coBool; - def.tooltip = L("Always ask for unsaved changes in presets when creating new project"); - def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_new_project") == "none" }); - option = Option(def, "default_action_on_new_project"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "default_action_on_new_project", + L("Ask for unsaved changes in presets when creating new project"), + L("Always ask for unsaved changes in presets when creating new project"), + app_config->get("default_action_on_new_project") == "none"); } #ifdef _WIN32 else { - def.label = L("Associate .gcode files to PrusaSlicer G-code Viewer"); - def.type = coBool; - def.tooltip = L("If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files."); - def.set_default_value(new ConfigOptionBool(app_config->get("associate_gcode") == "1")); - option = Option(def, "associate_gcode"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "associate_gcode", + L("Associate .gcode files to PrusaSlicer G-code Viewer"), + L("If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files."), + app_config->get("associate_gcode") == "1"); } #endif // _WIN32 #if __APPLE__ - def.label = L("Use Retina resolution for the 3D scene"); - def.type = coBool; - def.tooltip = L("If enabled, the 3D scene will be rendered in Retina resolution. " - "If you are experiencing 3D performance problems, disabling this option may help."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_retina_opengl") == "1" }); - option = Option (def, "use_retina_opengl"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "use_retina_opengl", + L("Use Retina resolution for the 3D scene"), + L("If enabled, the 3D scene will be rendered in Retina resolution. " + "If you are experiencing 3D performance problems, disabling this option may help."), + app_config->get("use_retina_opengl") == "1"); #endif m_optgroup_general->append_separator(); // Show/Hide splash screen - def.label = L("Show splash screen"); - def.type = coBool; - def.tooltip = L("Show splash screen"); - def.set_default_value(new ConfigOptionBool{ app_config->get("show_splash_screen") == "1" }); - option = Option(def, "show_splash_screen"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "show_splash_screen", + L("Show splash screen"), + L("Show splash screen"), + app_config->get("show_splash_screen") == "1"); - def.label = L("Restore window position on start"); - def.type = coBool; - def.tooltip = L("If enabled, PrusaSlicer will be open at the position it was closed"); - def.set_default_value(new ConfigOptionBool{ app_config->get("restore_win_position") == "1" }); - option = Option(def, "restore_win_position"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "restore_win_position", + L("Restore window position on start"), + L("If enabled, PrusaSlicer will be open at the position it was closed"), + app_config->get("restore_win_position") == "1"); // Clear Undo / Redo stack on new project - def.label = L("Clear Undo / Redo stack on new project"); - def.type = coBool; - def.tooltip = L("Clear Undo / Redo stack on new project or when an existing project is loaded."); - def.set_default_value(new ConfigOptionBool{ app_config->get("clear_undo_redo_stack_on_new_project") == "1" }); - option = Option(def, "clear_undo_redo_stack_on_new_project"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "clear_undo_redo_stack_on_new_project", + L("Clear Undo / Redo stack on new project"), + L("Clear Undo / Redo stack on new project or when an existing project is loaded."), + app_config->get("clear_undo_redo_stack_on_new_project") == "1"); #if defined(_WIN32) || defined(__APPLE__) - def.label = L("Enable support for legacy 3DConnexion devices"); - def.type = coBool; - def.tooltip = L("If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M"); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_legacy_3DConnexion") == "1" }); - option = Option(def, "use_legacy_3DConnexion"); - m_optgroup_general->append_single_option_line(option); + append_bool_option(m_optgroup_general, "use_legacy_3DConnexion", + L("Enable support for legacy 3DConnexion devices"), + L("If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M"), + app_config->get("use_legacy_3DConnexion") == "1"); #endif // _WIN32 || __APPLE__ activate_options_tab(m_optgroup_general); // Add "Camera" tab - m_optgroup_camera = create_options_tab(_L("Camera"), tabs); + m_optgroup_camera = create_options_tab(L("Camera"), tabs); m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; - def.label = L("Use perspective camera"); - def.type = coBool; - def.tooltip = L("If enabled, use perspective camera. If not enabled, use orthographic camera."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_perspective_camera") == "1" }); - option = Option(def, "use_perspective_camera"); - m_optgroup_camera->append_single_option_line(option); + append_bool_option(m_optgroup_camera, "use_perspective_camera", + L("Use perspective camera"), + L("If enabled, use perspective camera. If not enabled, use orthographic camera."), + app_config->get("use_perspective_camera") == "1"); - def.label = L("Use free camera"); - def.type = coBool; - def.tooltip = L("If enabled, use free camera. If not enabled, use constrained camera."); - def.set_default_value(new ConfigOptionBool(app_config->get("use_free_camera") == "1")); - option = Option(def, "use_free_camera"); - m_optgroup_camera->append_single_option_line(option); + append_bool_option(m_optgroup_camera, "use_free_camera", + L("Use free camera"), + L("If enabled, use free camera. If not enabled, use constrained camera."), + app_config->get("use_free_camera") == "1"); - def.label = L("Reverse direction of zoom with mouse wheel"); - def.type = coBool; - def.tooltip = L("If enabled, reverses the direction of zoom with mouse wheel"); - def.set_default_value(new ConfigOptionBool(app_config->get("reverse_mouse_wheel_zoom") == "1")); - option = Option(def, "reverse_mouse_wheel_zoom"); - m_optgroup_camera->append_single_option_line(option); + append_bool_option(m_optgroup_camera, "reverse_mouse_wheel_zoom", + L("Reverse direction of zoom with mouse wheel"), + L("If enabled, reverses the direction of zoom with mouse wheel"), + app_config->get("reverse_mouse_wheel_zoom") == "1"); activate_options_tab(m_optgroup_camera); // Add "GUI" tab - m_optgroup_gui = create_options_tab(_L("GUI"), tabs); - m_optgroup_gui->m_on_change = [this, tabs](t_config_option_key opt_key, boost::any value) { + m_optgroup_gui = create_options_tab(L("GUI"), tabs); + m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { if (opt_key == "suppress_hyperlinks") m_values[opt_key] = boost::any_cast(value) ? "1" : ""; else if (opt_key == "notify_release") { @@ -363,166 +379,124 @@ void PreferencesDialog::build(size_t selected_tab) } }; - def.label = L("Sequential slider applied only to top layer"); - def.type = coBool; - def.tooltip = L("If enabled, changes made using the sequential slider, in preview, apply only to gcode top layer. " - "If disabled, changes made using the sequential slider, in preview, apply to the whole gcode."); - def.set_default_value(new ConfigOptionBool{ app_config->get("seq_top_layer_only") == "1" }); - option = Option(def, "seq_top_layer_only"); - m_optgroup_gui->append_single_option_line(option); + append_bool_option(m_optgroup_gui, "seq_top_layer_only", + L("Sequential slider applied only to top layer"), + L("If enabled, changes made using the sequential slider, in preview, apply only to gcode top layer." + "If disabled, changes made using the sequential slider, in preview, apply to the whole gcode."), + app_config->get("seq_top_layer_only") == "1"); if (is_editor) { - def.label = L("Show sidebar collapse/expand button"); - def.type = coBool; - def.tooltip = L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"); - def.set_default_value(new ConfigOptionBool{ app_config->get("show_collapse_button") == "1" }); - option = Option(def, "show_collapse_button"); - m_optgroup_gui->append_single_option_line(option); + append_bool_option(m_optgroup_gui, "show_collapse_button", + L("Show sidebar collapse/expand button"), + L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"), + app_config->get("show_collapse_button") == "1"); - def.label = L("Suppress to open hyperlink in browser"); - def.type = coBool; - def.tooltip = L("If enabled, PrusaSlicer will not open a hyperlinks in your browser."); - //def.tooltip = ("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. " - // "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."); - def.set_default_value(new ConfigOptionBool{ app_config->get("suppress_hyperlinks") == "1" }); - option = Option(def, "suppress_hyperlinks"); - m_optgroup_gui->append_single_option_line(option); + append_bool_option(m_optgroup_gui, "suppress_hyperlinks", + L("Suppress to open hyperlink in browser"), + L("If enabled, PrusaSlicer will not open a hyperlinks in your browser."), + //L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. " + // "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."), + app_config->get("suppress_hyperlinks") == "1"); - def.label = L("Use colors for axes values in Manipulation panel"); - def.type = coBool; - def.tooltip = L("If enabled, the axes names and axes values will be colorized according to the axes colors. " - "If disabled, old UI will be used."); - def.set_default_value(new ConfigOptionBool{ app_config->get("color_mapinulation_panel") == "1" }); - option = Option(def, "color_mapinulation_panel"); - m_optgroup_gui->append_single_option_line(option); + append_bool_option(m_optgroup_gui, "color_mapinulation_panel", + L("Use colors for axes values in Manipulation panel"), + L("If enabled, the axes names and axes values will be colorized according to the axes colors. " + "If disabled, old UI will be used."), + app_config->get("color_mapinulation_panel") == "1"); - def.label = L("Order object volumes by types"); - def.type = coBool; - def.tooltip = L("If enabled, volumes will be always ordered inside the object. Correct order is Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. " - "If disabled, you can reorder Model Parts, Negative Volumes and Modifiers. But one of the model parts have to be on the first place."); - def.set_default_value(new ConfigOptionBool{ app_config->get("order_volumes") == "1" }); - option = Option(def, "order_volumes"); - m_optgroup_gui->append_single_option_line(option); + append_bool_option(m_optgroup_gui, "order_volumes", + L("Order object volumes by types"), + L("If enabled, volumes will be always ordered inside the object. Correct order is Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. " + "If disabled, you can reorder Model Parts, Negative Volumes and Modifiers. But one of the model parts have to be on the first place."), + app_config->get("order_volumes") == "1"); #ifdef _MSW_DARK_MODE - def.label = L("Set settings tabs as menu items (experimental)"); - def.type = coBool; - def.tooltip = L("If enabled, Settings Tabs will be placed as menu items. " - "If disabled, old UI will be used."); - def.set_default_value(new ConfigOptionBool{ app_config->get("tabs_as_menu") == "1" }); - option = Option(def, "tabs_as_menu"); - m_optgroup_gui->append_single_option_line(option); + append_bool_option(m_optgroup_gui, "tabs_as_menu", + L("Set settings tabs as menu items (experimental)"), + L("If enabled, Settings Tabs will be placed as menu items. If disabled, old UI will be used."), + app_config->get("tabs_as_menu") == "1"); #endif m_optgroup_gui->append_separator(); - def.label = L("Show \"Tip of the day\" notification after start"); - def.type = coBool; - def.tooltip = L("If enabled, useful hints are displayed at startup."); - def.set_default_value(new ConfigOptionBool{ app_config->get("show_hints") == "1" }); - option = Option(def, "show_hints"); - m_optgroup_gui->append_single_option_line(option); + append_bool_option(m_optgroup_gui, "show_hints", + L("Show \"Tip of the day\" notification after start"), + L("If enabled, useful hints are displayed at startup."), + app_config->get("show_hints") == "1"); - ConfigOptionDef def_enum; - def_enum.label = L("Notify about new releases"); - def_enum.type = coEnum; - def_enum.tooltip = L("You will be notified about new release after startup acordingly: All = Regular release and alpha / beta releases. Release only = regular release."); - def_enum.enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def_enum.enum_values.push_back("all"); - def_enum.enum_values.push_back("release"); - def_enum.enum_values.push_back("none"); - def_enum.enum_labels.push_back(L("All")); - def_enum.enum_labels.push_back(L("Release only")); - def_enum.enum_labels.push_back(L("None")); - def_enum.mode = comSimple; - def_enum.set_default_value(new ConfigOptionEnum(static_cast(s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release"))))); - option = Option(def_enum, "notify_release"); - m_optgroup_gui->append_single_option_line(option); + append_enum_option(m_optgroup_gui, "notify_release", + L("Notify about new releases"), + L("You will be notified about new release after startup acordingly: All = Regular release and alpha / beta releases. Release only = regular release."), + new ConfigOptionEnum(static_cast(s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")))), + &ConfigOptionEnum::get_enum_values(), + {"all", "release", "none"}, + {L("All"), L("Release only"), L("None")}); m_optgroup_gui->append_separator(); - def.label = L("Use custom size for toolbar icons"); - def.type = coBool; - def.tooltip = L("If enabled, you can change size of toolbar icons manually."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" }); - option = Option(def, "use_custom_toolbar_size"); - m_optgroup_gui->append_single_option_line(option); - + append_bool_option(m_optgroup_gui, "use_custom_toolbar_size", + L("Use custom size for toolbar icons"), + L("If enabled, you can change size of toolbar icons manually."), + app_config->get("use_custom_toolbar_size") == "1"); } activate_options_tab(m_optgroup_gui); - // set Field for notify_release to its value to activate the object - if (is_editor) { - boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); - m_optgroup_gui->get_field("notify_release")->set_value(val, false); - } if (is_editor) { + // set Field for notify_release to its value to activate the object + boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); + m_optgroup_gui->get_field("notify_release")->set_value(val, false); + create_icon_size_slider(); m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); create_settings_mode_widget(); create_settings_text_color_widget(); - } #if ENABLE_ENVIRONMENT_MAP - if (is_editor) { // Add "Render" tab - m_optgroup_render = create_options_tab(_L("Render"), tabs); + m_optgroup_render = create_options_tab(L("Render"), tabs); m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; - def.label = L("Use environment map"); - def.type = coBool; - def.tooltip = L("If enabled, renders object using the environment map."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_environment_map") == "1" }); - option = Option(def, "use_environment_map"); - m_optgroup_render->append_single_option_line(option); + append_bool_option(m_optgroup_render, "use_environment_map", + L("Use environment map"), + L("If enabled, renders object using the environment map."), + app_config->get("use_environment_map") == "1"); activate_options_tab(m_optgroup_render); - } #endif // ENABLE_ENVIRONMENT_MAP #ifdef _WIN32 - // Add "Dark Mode" tab - { // Add "Dark Mode" tab m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs); m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) { m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; - def.label = L("Enable dark mode"); - def.type = coBool; - def.tooltip = L("If enabled, UI will use Dark mode colors. " - "If disabled, old UI will be used."); - def.set_default_value(new ConfigOptionBool{ app_config->get("dark_color_mode") == "1" }); - option = Option(def, "dark_color_mode"); - m_optgroup_dark_mode->append_single_option_line(option); + append_bool_option(m_optgroup_dark_mode, "dark_color_mode", + L("Enable dark mode"), + L("If enabled, UI will use Dark mode colors. If disabled, old UI will be used."), + app_config->get("dark_color_mode") == "1"); if (wxPlatformInfo::Get().GetOSMajorVersion() >= 10) // Use system menu just for Window newer then Windows 10 // Use menu with ownerdrawn items by default on systems older then Windows 10 { - def.label = L("Use system menu for application"); - def.type = coBool; - def.tooltip = L("If enabled, application will use the standard Windows system menu,\n" - "but on some combination of display scales it can looks ugly. If disabled, old UI will be used."); - def.set_default_value(new ConfigOptionBool{ app_config->get("sys_menu_enabled") == "1" }); - option = Option(def, "sys_menu_enabled"); - m_optgroup_dark_mode->append_single_option_line(option); + append_bool_option(m_optgroup_dark_mode, "sys_menu_enabled", + L("Use system menu for application"), + L("If enabled, application will use the standart Windows system menu,\n" + "but on some combination od display scales it can look ugly. If disabled, old UI will be used."), + app_config->get("sys_menu_enabled") == "1"); } activate_options_tab(m_optgroup_dark_mode); - } #endif //_WIN32 + } // update alignment of the controls for all tabs update_ctrls_alignment(); - if (selected_tab < tabs->GetPageCount()) - tabs->SetSelection(selected_tab); - auto sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(tabs, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); @@ -547,7 +521,10 @@ std::vector PreferencesDialog::optgroups() #ifdef _WIN32 , m_optgroup_dark_mode.get() #endif // _WIN32 - }) +#if ENABLE_ENVIRONMENT_MAP + , m_optgroup_render.get() +#endif // ENABLE_ENVIRONMENT_MAP + }) if (opt) out.emplace_back(opt); return out; @@ -567,9 +544,6 @@ void PreferencesDialog::update_ctrls_alignment() void PreferencesDialog::accept(wxEvent&) { -// if (m_values.find("no_defaults") != m_values.end() -// warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME)); - std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" }; for (const std::string& option : options_to_recreate_GUI) { @@ -655,16 +629,29 @@ void PreferencesDialog::accept(wxEvent&) wxGetApp().update_ui_from_settings(); } -void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) +void PreferencesDialog::msw_rescale() { for (ConfigOptionsGroup* og : this->optgroups()) og->msw_rescale(); +#ifdef _WIN32 + m_optgroup_dark_mode->msw_rescale(); +#endif //_WIN32 +#if ENABLE_ENVIRONMENT_MAP + m_optgroup_render->msw_rescale(); +#endif // ENABLE_ENVIRONMENT_MAP msw_buttons_rescale(this, em_unit(), { wxID_OK, wxID_CANCEL }); layout(); } +void PreferencesDialog::on_sys_color_changed() +{ +#ifdef _WIN32 + wxGetApp().UpdateDlgDarkUI(this); +#endif +} + void PreferencesDialog::layout() { const int em = em_unit(); @@ -760,7 +747,8 @@ void PreferencesDialog::create_settings_mode_widget() wxWindow* parent = m_optgroup_gui->parent(); wxGetApp().UpdateDarkUI(parent); - wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _L("Layout Options")); + wxString title = L("Layout Options"); + wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title)); wxGetApp().UpdateDarkUI(stb); if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); stb->SetFont(wxGetApp().normal_font()); @@ -794,36 +782,58 @@ void PreferencesDialog::create_settings_mode_widget() id++; } + std::string opt_key = "settings_layout_mode"; + m_blinkers[opt_key] = new BlinkingBitmap(this); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL); sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); + + append_preferences_option_to_searcer(m_optgroup_gui, opt_key, title); } void PreferencesDialog::create_settings_text_color_widget() { wxWindow* parent = m_optgroup_gui->parent(); - wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _L("Text colors")); + wxString title = L("Text colors"); + wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title)); wxGetApp().UpdateDarkUI(stb); if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); - wxSizer* sizer = new wxStaticBoxSizer(stb, wxVERTICAL); - ButtonsDescription::FillSizerWithTextColorDescriptions(sizer, parent, &m_sys_colour, &m_mod_colour); + std::string opt_key = "text_colors"; + m_blinkers[opt_key] = new BlinkingBitmap(this); + + wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); + ButtonsDescription::FillSizerWithTextColorDescriptions(stb_sizer, parent, &m_sys_colour, &m_mod_colour); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); + + append_preferences_option_to_searcer(m_optgroup_gui, opt_key, title); } void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key) { - m_highlighter.set_timer_owner(this, 0); - this->Bind(wxEVT_TIMER, [this](wxTimerEvent&) - { - m_highlighter.blink(); - }); + if (m_blinkers.find(opt_key) != m_blinkers.end()) + if (BlinkingBitmap* blinker = m_blinkers.at(opt_key); blinker) { + m_highlighter.init(blinker); + return; + } - std::pair ctrl = { nullptr, nullptr }; - for (ConfigOptionsGroup* opt_group : this->optgroups()) { - ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1); + for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui +#ifdef _WIN32 + , m_optgroup_dark_mode +#endif // _WIN32 +#if ENABLE_ENVIRONMENT_MAP + , m_optgroup_render +#endif // ENABLE_ENVIRONMENT_MAP + }) { + std::pair ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1); if (ctrl.first && ctrl.second) { m_highlighter.init(ctrl); break; @@ -831,53 +841,5 @@ void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key) } } -void PreferencesDialog::PreferencesHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) -{ - m_timer.SetOwner(owner, timerid); -} - -void PreferencesDialog::PreferencesHighlighter::init(std::pair params) -{ - if (m_timer.IsRunning()) - invalidate(); - if (!params.first || !params.second) - return; - - m_timer.Start(300, false); - - m_custom_ctrl = params.first; - m_show_blink_ptr = params.second; - - *m_show_blink_ptr = true; - m_custom_ctrl->Refresh(); -} - -void PreferencesDialog::PreferencesHighlighter::invalidate() -{ - m_timer.Stop(); - - if (m_custom_ctrl && m_show_blink_ptr) { - *m_show_blink_ptr = false; - m_custom_ctrl->Refresh(); - m_show_blink_ptr = nullptr; - m_custom_ctrl = nullptr; - } - - m_blink_counter = 0; -} - -void PreferencesDialog::PreferencesHighlighter::blink() -{ - if (m_custom_ctrl && m_show_blink_ptr) { - *m_show_blink_ptr = !*m_show_blink_ptr; - m_custom_ctrl->Refresh(); - } - else - return; - - if ((++m_blink_counter) == 11) - invalidate(); -} - } // GUI } // Slic3r diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 6c80121950..f8b1d1237a 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -3,6 +3,7 @@ #include "GUI.hpp" #include "GUI_Utils.hpp" +#include "wxExtensions.hpp" #include #include @@ -10,6 +11,7 @@ #include class wxColourPickerCtrl; +class wxBookCtrlBase; namespace Slic3r { @@ -39,24 +41,29 @@ class PreferencesDialog : public DPIDialog wxSizer* m_icon_size_sizer; wxColourPickerCtrl* m_sys_colour {nullptr}; wxColourPickerCtrl* m_mod_colour {nullptr}; + wxBookCtrlBase* tabs {nullptr}; + bool isOSX {false}; bool m_settings_layout_changed {false}; bool m_seq_top_layer_only_changed{ false }; bool m_recreate_GUI{false}; public: - explicit PreferencesDialog(wxWindow* parent, int selected_tab = 0, const std::string& highlight_opt_key = std::string()); + explicit PreferencesDialog(wxWindow* paren); ~PreferencesDialog() = default; bool settings_layout_changed() const { return m_settings_layout_changed; } bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; } bool recreate_GUI() const { return m_recreate_GUI; } - void build(size_t selected_tab = 0); + void build(); void update_ctrls_alignment(); void accept(wxEvent&); + void show(const std::string& highlight_option = std::string(), const std::string& tab_name = std::string()); protected: - void on_dpi_changed(const wxRect &suggested_rect) override; + void msw_rescale(); + void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); } + void on_sys_color_changed() override; void layout(); void create_icon_size_slider(); void create_settings_mode_widget(); @@ -64,20 +71,8 @@ protected: void init_highlighter(const t_config_option_key& opt_key); std::vector optgroups(); - struct PreferencesHighlighter - { - void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY); - void init(std::pair); - void blink(); - void invalidate(); - - private: - OG_CustomCtrl* m_custom_ctrl{ nullptr }; - bool* m_show_blink_ptr{ nullptr }; - int m_blink_counter{ 0 }; - wxTimer m_timer; - } - m_highlighter; + HighlighterForWx m_highlighter; + std::map m_blinkers; }; } // GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 2b53883be0..ed4888a878 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -24,6 +24,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" @@ -441,15 +442,14 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con // Paint a red flag for incompatible presets. bmps.emplace_back(is_compatible ? bitmap_cache().mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); - if (m_type == Preset::TYPE_FILAMENT && !filament_rgb.empty()) - { - unsigned char rgb[3]; + if (m_type == Preset::TYPE_FILAMENT && !filament_rgb.empty()) { // Paint the color bars. - bitmap_cache().parse_color(filament_rgb, rgb); - bmps.emplace_back(bitmap_cache().mksolid(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, rgb, false, 1, dark_mode)); + ColorRGB color; + decode_color(filament_rgb, color); + bmps.emplace_back(bitmap_cache().mksolid(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, color, false, 1, dark_mode)); if (!is_single_bar) { - bitmap_cache().parse_color(extruder_rgb, rgb); - bmps.emplace_back(bitmap_cache().mksolid(thin_icon_width, icon_height, rgb, false, 1, dark_mode)); + decode_color(extruder_rgb, color); + bmps.emplace_back(bitmap_cache().mksolid(thin_icon_width, icon_height, color, false, 1, dark_mode)); } // Paint a lock at the system presets. bmps.emplace_back(bitmap_cache().mkclear(space_icon_width, icon_height)); @@ -767,11 +767,9 @@ void PlaterPresetComboBox::update() const Preset* selected_filament_preset = nullptr; std::string extruder_color; - if (m_type == Preset::TYPE_FILAMENT) - { - unsigned char rgb[3]; + if (m_type == Preset::TYPE_FILAMENT) { extruder_color = m_preset_bundle->printers.get_edited_preset().config.opt_string("extruder_colour", (unsigned int)m_extruder_idx); - if (!bitmap_cache().parse_color(extruder_color, rgb)) + if (!can_decode_color(extruder_color)) // Extruder color is not defined. extruder_color.clear(); selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index add5dc0024..bdb2e55bf9 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -43,6 +43,8 @@ static char marker_by_type(Preset::Type type, PrinterTechnology pt) return ImGui::MaterialIconMarker; case Preset::TYPE_PRINTER: return pt == ptSLA ? ImGui::PrinterSlaIconMarker : ImGui::PrinterIconMarker; + case Preset::TYPE_PREFERENCES: + return ImGui::PreferencesButton; default: return ' '; } @@ -302,6 +304,9 @@ void OptionsSearcher::init(std::vector input_values) options.clear(); for (auto i : input_values) append_options(i.config, i.type, i.mode); + + options.insert(options.end(), preferences_options.begin(), preferences_options.end()); + sort_options(); search(search_line, true); @@ -323,6 +328,47 @@ void OptionsSearcher::apply(DynamicPrintConfig* config, Preset::Type type, Confi search(search_line, true); } +void OptionsSearcher::append_preferences_option(const GUI::Line& opt_line) +{ + Preset::Type type = Preset::TYPE_PREFERENCES; + wxString label = opt_line.label; + if (label.IsEmpty()) + return; + + std::string key = get_key(opt_line.get_options().front().opt_id, type); + const GroupAndCategory& gc = groups_and_categories[key]; + if (gc.group.IsEmpty() || gc.category.IsEmpty()) + return; + + preferences_options.emplace_back(Search::Option{ boost::nowide::widen(key), type, + label.ToStdWstring(), _(label).ToStdWstring(), + gc.group.ToStdWstring(), _(gc.group).ToStdWstring(), + gc.category.ToStdWstring(), _(gc.category).ToStdWstring() }); +} + +void OptionsSearcher::append_preferences_options(const std::vector& opt_lines) +{ + //Preset::Type type = Preset::TYPE_PREFERENCES; + for (const GUI::Line& line : opt_lines) { + if (line.is_separator()) + continue; + append_preferences_option(line); + //wxString label = line.label; + //if (label.IsEmpty()) + // continue; + + //std::string key = get_key(line.get_options().front().opt_id, type); + //const GroupAndCategory& gc = groups_and_categories[key]; + //if (gc.group.IsEmpty() || gc.category.IsEmpty()) + // continue; + // + //preferences_options.emplace_back(Search::Option{ boost::nowide::widen(key), type, + // label.ToStdWstring(), _(label).ToStdWstring(), + // gc.group.ToStdWstring(), _(gc.group).ToStdWstring(), + // gc.category.ToStdWstring(), _(gc.category).ToStdWstring() }); + } +} + const Option& OptionsSearcher::get_option(size_t pos_in_filter) const { assert(pos_in_filter != size_t(-1) && found[pos_in_filter].option_idx != size_t(-1)); @@ -429,6 +475,7 @@ static const std::map icon_idxs = { {ImGui::PrinterSlaIconMarker, 2}, {ImGui::FilamentIconMarker , 3}, {ImGui::MaterialIconMarker , 4}, + {ImGui::PreferencesButton , 5}, }; SearchDialog::SearchDialog(OptionsSearcher* searcher) @@ -717,7 +764,7 @@ void SearchDialog::on_sys_color_changed() SearchListModel::SearchListModel(wxWindow* parent) : wxDataViewVirtualListModel(0) { int icon_id = 0; - for (const std::string& icon : { "cog", "printer", "sla_printer", "spool", "resin" }) + for (const std::string& icon : { "cog", "printer", "sla_printer", "spool", "resin", "notification_preferences" }) m_icon[icon_id++] = ScalableBitmap(parent, icon); } diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index d5add92629..47385e879a 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -17,6 +17,7 @@ #include "GUI_Utils.hpp" #include "wxExtensions.hpp" +#include "OptionsGroup.hpp" #include "libslic3r/Preset.hpp" @@ -85,6 +86,7 @@ class OptionsSearcher PrinterTechnology printer_technology; std::vector