diff options
Diffstat (limited to 'src')
120 files changed, 2795 insertions, 1966 deletions
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index ec5ce3702..5b4bb34a3 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -175,6 +175,9 @@ void AppConfig::set_defaults() if (get("show_splash_screen").empty()) set("show_splash_screen", "1"); + if (get("restore_win_position").empty()) + set("restore_win_position", "1"); // allowed values - "1", "0", "crashed_at_..." + if (get("show_hints").empty()) set("show_hints", "1"); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 418f3427b..cd0f1a5ae 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -100,7 +100,7 @@ public: bool has_section(const std::string §ion) const { return m_storage.find(section) != m_storage.end(); } const std::map<std::string, std::string>& get_section(const std::string §ion) const - { return m_storage.find(section)->second; } + { auto it = m_storage.find(section); assert(it != m_storage.end()); return it->second; } void set_section(const std::string §ion, const std::map<std::string, std::string>& data) { m_storage[section] = data; } void clear_section(const std::string §ion) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 97d1d5ce5..c79be847b 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -29,6 +29,8 @@ set(SLIC3R_SOURCES clipper.hpp ClipperUtils.cpp ClipperUtils.hpp + Color.cpp + Color.hpp Config.cpp Config.hpp EdgeGrid.cpp diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp new file mode 100644 index 000000000..7b5a7f3ba --- /dev/null +++ b/src/libslic3r/Color.cpp @@ -0,0 +1,400 @@ +#include "libslic3r.h" +#include "Color.hpp" + +#include <random> + +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<float> 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<std::string>& colors_in, std::vector<ColorRGB>& colors_out) +{ + colors_out = std::vector<ColorRGB>(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<std::string>& colors_in, std::vector<ColorRGBA>& colors_out) +{ + colors_out = std::vector<ColorRGBA>(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 000000000..fce0c67e0 --- /dev/null +++ b/src/libslic3r/Color.hpp @@ -0,0 +1,170 @@ +#ifndef slic3r_Color_hpp_ +#define slic3r_Color_hpp_ + +#include <array> +#include <algorithm> + +namespace Slic3r { + +class ColorRGB +{ + std::array<float, 3> 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<unsigned char>(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast<unsigned char>(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast<unsigned char>(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<float, 4> 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<unsigned char>(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast<unsigned char>(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast<unsigned char>(m_data[2] * 255.0f); } + unsigned char a_uchar() const { return static_cast<unsigned char>(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<std::string>& colors_in, std::vector<ColorRGB>& colors_out); +extern bool decode_colors(const std::vector<std::string>& colors_in, std::vector<ColorRGBA>& 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/Config.hpp b/src/libslic3r/Config.hpp index b8a838f0d..3aac187cb 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -758,6 +758,8 @@ public: ConfigOptionIntsTempl() : ConfigOptionVector<int>() {} explicit ConfigOptionIntsTempl(size_t n, int value) : ConfigOptionVector<int>(n, value) {} explicit ConfigOptionIntsTempl(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {} + explicit ConfigOptionIntsTempl(const std::vector<int> &v) : ConfigOptionVector<int>(v) {} + explicit ConfigOptionIntsTempl(std::vector<int> &&v) : ConfigOptionVector<int>(std::move(v)) {} static ConfigOptionType static_type() { return coInts; } ConfigOptionType type() const override { return static_type(); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index def30bbde..a91f15b2a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -677,7 +677,7 @@ namespace DoExport { print_statistics.total_weight = total_weight; print_statistics.total_cost = total_cost; - print_statistics.filament_stats = result.print_statistics.volumes_per_extruder; + print_statistics.filament_stats = result.print_statistics.volumes_per_extruder; } // if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found @@ -984,19 +984,26 @@ namespace DoExport { static std::string update_print_stats_and_format_filament_stats( const bool has_wipe_tower, const WipeTowerData &wipe_tower_data, + const FullPrintConfig &config, const std::vector<Extruder> &extruders, + unsigned int initial_extruder_id, PrintStatistics &print_statistics) { std::string filament_stats_string_out; print_statistics.clear(); print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); + print_statistics.initial_extruder_id = initial_extruder_id; + std::vector<std::string> filament_types; if (! extruders.empty()) { std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0); std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0); std::pair<std::string, unsigned int> out_filament_used_g ("; filament used [g] = ", 0); std::pair<std::string, unsigned int> out_filament_cost ("; filament cost = ", 0); for (const Extruder &extruder : extruders) { + print_statistics.printing_extruders.emplace_back(extruder.id()); + filament_types.emplace_back(config.filament_type.get_at(extruder.id())); + double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter double filament_weight = extruded_volume * extruder.filament_density() * 0.001; @@ -1036,6 +1043,13 @@ namespace DoExport { filament_stats_string_out += "\n" + out_filament_used_g.first; if (out_filament_cost.second) filament_stats_string_out += "\n" + out_filament_cost.first; + print_statistics.initial_filament_type = config.filament_type.get_at(initial_extruder_id); + std::sort(filament_types.begin(), filament_types.end()); + print_statistics.printing_filament_types = filament_types.front(); + for (size_t i = 1; i < filament_types.size(); ++ i) { + print_statistics.printing_filament_types += ","; + print_statistics.printing_filament_types += filament_types[i]; + } } return filament_stats_string_out; } @@ -1486,7 +1500,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write(DoExport::update_print_stats_and_format_filament_stats( // Const inputs has_wipe_tower, print.wipe_tower_data(), + this->config(), m_writer.extruders(), + initial_extruder_id, // Modifies print.m_print_statistics)); file.write("\n"); @@ -1685,40 +1701,56 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print) { - if ((print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) + const GCodeFlavor flavor = print.config().gcode_flavor.value; + if ( (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware) && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { + int factor = flavor == gcfRepRapFirmware ? 60 : 1; // RRF M203 and M566 are in mm/min file.write_format("M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", int(print.config().machine_max_acceleration_x.values.front() + 0.5), int(print.config().machine_max_acceleration_y.values.front() + 0.5), int(print.config().machine_max_acceleration_z.values.front() + 0.5), int(print.config().machine_max_acceleration_e.values.front() + 0.5)); - file.write_format("M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n", - int(print.config().machine_max_feedrate_x.values.front() + 0.5), - int(print.config().machine_max_feedrate_y.values.front() + 0.5), - int(print.config().machine_max_feedrate_z.values.front() + 0.5), - int(print.config().machine_max_feedrate_e.values.front() + 0.5)); + file.write_format("M203 X%d Y%d Z%d E%d ; sets maximum feedrates, %s\n", + int(print.config().machine_max_feedrate_x.values.front() * factor + 0.5), + int(print.config().machine_max_feedrate_y.values.front() * factor + 0.5), + int(print.config().machine_max_feedrate_z.values.front() * factor + 0.5), + int(print.config().machine_max_feedrate_e.values.front() * factor + 0.5), + factor == 60 ? "mm / min" : "mm / sec"); // Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about // backwards compatibility: https://github.com/prusa3d/PrusaSlicer/issues/1089 // Legacy Marlin should export travel acceleration the same as printing acceleration. // MarlinFirmware has the two separated. - int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy + int travel_acc = flavor == gcfMarlinLegacy ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); - file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", - int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), - int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), - travel_acc); + // Retract acceleration not accepted in M204 in RRF + if (flavor == gcfRepRapFirmware) + file.write_format("M204 P%d T%d ; sets acceleration (P, T), mm/sec^2\n", + int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), + travel_acc); + else + file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", + int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), + int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), + travel_acc); assert(is_decimal_separator_point()); - file.write_format("M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", - print.config().machine_max_jerk_x.values.front(), - print.config().machine_max_jerk_y.values.front(), - print.config().machine_max_jerk_z.values.front(), - print.config().machine_max_jerk_e.values.front()); - file.write_format("M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n", - int(print.config().machine_min_extruding_rate.values.front() + 0.5), - int(print.config().machine_min_travel_rate.values.front() + 0.5)); + file.write_format(flavor == gcfRepRapFirmware + ? "M566 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/min\n" + : "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", + print.config().machine_max_jerk_x.values.front() * factor, + print.config().machine_max_jerk_y.values.front() * factor, + print.config().machine_max_jerk_z.values.front() * factor, + print.config().machine_max_jerk_e.values.front() * factor); + if (flavor != gcfRepRapFirmware) + file.write_format("M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n", + int(print.config().machine_min_extruding_rate.values.front() + 0.5), + int(print.config().machine_min_travel_rate.values.front() + 0.5)); + else { + // M205 Sn Tn not supported in RRF. They use M203 Inn to set minimum feedrate for + // all moves. This is currently not implemented. + } } } diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 5657b60fb..301577e58 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -811,13 +811,13 @@ std::string CoolingBuffer::apply_layer_cooldown( // Skip the non-whitespaces of the F parameter up the comment or end of line. for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos); // Append the rest of the line without the comment. - if (fpos < end) - // The G-code line is not empty yet. Emit the rest of it. - new_gcode.append(fpos, end - fpos); - else if (remove && new_gcode == "G1") { + if (remove && (fpos == end || *fpos == '\n') && (new_gcode == "G1" || boost::ends_with(new_gcode, "\nG1"))) { // The G-code line only contained the F word, now it is empty. Remove it completely including the comments. new_gcode.resize(new_gcode.size() - 2); end = line_end; + } else { + // The G-code line may not be empty yet. Emit the rest of it. + new_gcode.append(fpos, end - fpos); } } // Process the rest of the line. @@ -845,6 +845,8 @@ std::string CoolingBuffer::apply_layer_cooldown( if (pos < gcode_end) new_gcode.append(pos, gcode_end - pos); + // There should be no empty G1 lines emitted. + assert(new_gcode.find("G1\n") == std::string::npos); return new_gcode; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index ff4831a6c..1f19a548e 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -737,6 +737,9 @@ void GCodeProcessorResult::reset() { filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); custom_gcode_per_print_z = std::vector<CustomGCode::Item>(); +#if ENABLE_SPIRAL_VASE_LAYERS + spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>(); +#endif // ENABLE_SPIRAL_VASE_LAYERS time = 0; } #else @@ -752,6 +755,9 @@ void GCodeProcessorResult::reset() { filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); custom_gcode_per_print_z = std::vector<CustomGCode::Item>(); +#if ENABLE_SPIRAL_VASE_LAYERS + spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>(); +#endif // ENABLE_SPIRAL_VASE_LAYERS } #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -853,12 +859,17 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i)); } - if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { m_time_processor.machine_limits = reinterpret_cast<const MachineEnvelopeConfig&>(config); if (m_flavor == gcfMarlinLegacy) { // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. m_time_processor.machine_limits.machine_max_acceleration_travel = m_time_processor.machine_limits.machine_max_acceleration_extruding; } + if (m_flavor == gcfRepRapFirmware) { + // RRF does not support setting min feedrates. Set them to zero. + m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.); + m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.); + } } // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. @@ -893,6 +904,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_first_layer_height = std::abs(first_layer_height->value); m_result.max_print_height = config.max_print_height; + +#if ENABLE_SPIRAL_VASE_LAYERS + const ConfigOptionBool* spiral_vase = config.option<ConfigOptionBool>("spiral_vase"); + if (spiral_vase != nullptr) + m_spiral_vase_active = spiral_vase->value; +#endif // ENABLE_SPIRAL_VASE_LAYERS } void GCodeProcessor::apply_config(const DynamicPrintConfig& config) @@ -1020,7 +1037,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_limits_usage != nullptr) use_machine_limits = machine_limits_usage->value != MachineLimitsUsage::Ignore; - if (use_machine_limits && (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware)) { + if (use_machine_limits && (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware)) { const ConfigOptionFloats* machine_max_acceleration_x = config.option<ConfigOptionFloats>("machine_max_acceleration_x"); if (machine_max_acceleration_x != nullptr) m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; @@ -1087,12 +1104,22 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionFloats* machine_min_extruding_rate = config.option<ConfigOptionFloats>("machine_min_extruding_rate"); - if (machine_min_extruding_rate != nullptr) + if (machine_min_extruding_rate != nullptr) { m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; + if (m_flavor == gcfRepRapFirmware) { + // RRF does not support setting min feedrates. Set zero. + m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.); + } + } const ConfigOptionFloats* machine_min_travel_rate = config.option<ConfigOptionFloats>("machine_min_travel_rate"); - if (machine_min_travel_rate != nullptr) + if (machine_min_travel_rate != nullptr) { m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; + if (m_flavor == gcfRepRapFirmware) { + // RRF does not support setting min feedrates. Set zero. + m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.); + } + } } for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) { @@ -1126,6 +1153,12 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionFloat* max_print_height = config.option<ConfigOptionFloat>("max_print_height"); if (max_print_height != nullptr) m_result.max_print_height = max_print_height->value; + +#if ENABLE_SPIRAL_VASE_LAYERS + const ConfigOptionBool* spiral_vase = config.option<ConfigOptionBool>("spiral_vase"); + if (spiral_vase != nullptr) + m_spiral_vase_active = spiral_vase->value; +#endif // ENABLE_SPIRAL_VASE_LAYERS } void GCodeProcessor::enable_stealth_time_estimator(bool enabled) @@ -1188,6 +1221,10 @@ void GCodeProcessor::reset() m_options_z_corrector.reset(); +#if ENABLE_SPIRAL_VASE_LAYERS + m_spiral_vase_active = false; +#endif // ENABLE_SPIRAL_VASE_LAYERS + #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_mm3_per_mm_compare.reset(); m_height_compare.reset(); @@ -1888,6 +1925,16 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers // layer change tag if (comment == reserved_tag(ETags::Layer_Change)) { ++m_layer_id; +#if ENABLE_SPIRAL_VASE_LAYERS + if (m_spiral_vase_active) { + assert(!m_result.moves.empty()); + size_t move_id = m_result.moves.size() - 1; + if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first) + m_result.spiral_vase_layers.back().second.second = move_id; + else + m_result.spiral_vase_layers.push_back({ m_end_position[Z], { move_id, move_id } }); + } +#endif // ENABLE_SPIRAL_VASE_LAYERS return; } @@ -2693,6 +2740,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); } +#if ENABLE_SPIRAL_VASE_LAYERS + if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty() && !m_result.moves.empty()) + m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1; +#endif // ENABLE_SPIRAL_VASE_LAYERS + // store move store_move_vertex(type); } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 7377fca61..33b9a23f3 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -131,6 +131,9 @@ namespace Slic3r { std::vector<float> filament_densities; PrintEstimatedStatistics print_statistics; std::vector<CustomGCode::Item> custom_gcode_per_print_z; +#if ENABLE_SPIRAL_VASE_LAYERS + std::vector<std::pair<float, std::pair<size_t, size_t>>> spiral_vase_layers; +#endif // ENABLE_SPIRAL_VASE_LAYERS #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; @@ -541,6 +544,9 @@ namespace Slic3r { SeamsDetector m_seams_detector; OptionsZCorrector m_options_z_corrector; size_t m_last_default_color_id; +#if ENABLE_SPIRAL_VASE_LAYERS + bool m_spiral_vase_active; +#endif // ENABLE_SPIRAL_VASE_LAYERS #if ENABLE_GCODE_VIEWER_STATISTICS std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 354c909e1..233976b19 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -20,8 +20,10 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) this->config.apply(print_config, true); m_extrusion_axis = get_extrusion_axis(this->config); m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; - bool is_marlin = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware; - m_max_acceleration = std::lrint((is_marlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? + bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy + || print_config.gcode_flavor.value == gcfMarlinFirmware + || print_config.gcode_flavor.value == gcfRepRapFirmware; + m_max_acceleration = std::lrint((use_mach_limits && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? print_config.machine_max_acceleration_extruding.values.front() : 0); } diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 737514901..75537b6c6 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1797,19 +1797,28 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con line_end_f = facet[1] + t2 * (facet[2] - facet[1]); } - Point line_start(scale_(line_start_f.x()), scale_(line_start_f.y())); - Point line_end(scale_(line_end_f.x()), scale_(line_end_f.y())); - line_start -= print_object.center_offset(); - line_end -= print_object.center_offset(); + Line line_to_test(Point(scale_(line_start_f.x()), scale_(line_start_f.y())), + Point(scale_(line_end_f.x()), scale_(line_end_f.y()))); + line_to_test.translate(-print_object.center_offset()); + + // BoundingBoxes for EdgeGrids are computed from printable regions. It is possible that the painted line (line_to_test) could + // be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618). + // To ensure that the painted line is always inside EdgeGrid's BoundingBox, it is clipped by EdgeGrid's BoundingBox in cases + // when any of the endpoints of the line are outside the EdgeGrid's BoundingBox. + if (const BoundingBox &edge_grid_bbox = edge_grids[layer_idx].bbox(); !edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) { + // If the painted line (line_to_test) is entirely outside EdgeGrid's BoundingBox, skip this painted line. + if (!edge_grid_bbox.overlap(BoundingBox(Points{line_to_test.a, line_to_test.b})) || + !line_to_test.clip_with_bbox(edge_grid_bbox)) + continue; + } size_t mutex_idx = layer_idx & 0x3F; assert(mutex_idx < painted_lines_mutex.size()); PaintedLineVisitor visitor(edge_grids[layer_idx], painted_lines[layer_idx], painted_lines_mutex[mutex_idx], 16); - visitor.line_to_test.a = line_start; - visitor.line_to_test.b = line_end; - visitor.color = int(extruder_idx); - edge_grids[layer_idx].visit_cells_intersecting_line(line_start, line_end, visitor); + visitor.line_to_test = line_to_test; + visitor.color = int(extruder_idx); + edge_grids[layer_idx].visit_cells_intersecting_line(line_to_test.a, line_to_test.b, visitor); } } }); // end of parallel_for diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index acb4ee6a6..70c5756ef 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1248,6 +1248,13 @@ DynamicConfig PrintStatistics::config() const config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight)); config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost)); config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament)); + config.set_key_value("initial_tool", new ConfigOptionInt(int(this->initial_extruder_id))); + config.set_key_value("initial_extruder", new ConfigOptionInt(int(this->initial_extruder_id))); + config.set_key_value("initial_filament_type", new ConfigOptionString(this->initial_filament_type)); + config.set_key_value("printing_filament_types", new ConfigOptionString(this->printing_filament_types)); + config.set_key_value("num_printing_extruders", new ConfigOptionInt(int(this->printing_extruders.size()))); +// config.set_key_value("printing_extruders", new ConfigOptionInts(std::vector<int>(this->printing_extruders.begin(), this->printing_extruders.end()))); + return config; } @@ -1257,7 +1264,8 @@ DynamicConfig PrintStatistics::placeholders() for (const std::string &key : { "print_time", "normal_print_time", "silent_print_time", "used_filament", "extruded_volume", "total_cost", "total_weight", - "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"}) + "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament", + "initial_tool", "initial_extruder", "initial_filament_type", "printing_filament_types", "num_printing_extruders" }) config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); return config; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index e818cad0d..c2babd53c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -455,6 +455,10 @@ struct PrintStatistics double total_weight; double total_wipe_tower_cost; double total_wipe_tower_filament; + std::vector<unsigned int> printing_extruders; + unsigned int initial_extruder_id; + std::string initial_filament_type; + std::string printing_filament_types; std::map<size_t, double> filament_stats; // Config with the filled in print statistics. @@ -472,7 +476,11 @@ struct PrintStatistics total_weight = 0.; total_wipe_tower_cost = 0.; total_wipe_tower_filament = 0.; + initial_extruder_id = 0; + initial_filament_type.clear(); + printing_filament_types.clear(); filament_stats.clear(); + printing_extruders.clear(); } }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 612c9c096..34a06a18d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1614,7 +1614,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Use for time estimate")); def->enum_labels.push_back(L("Ignore")); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum<MachineLimitsUsage>(MachineLimitsUsage::EmitToGCode)); + def->set_default_value(new ConfigOptionEnum<MachineLimitsUsage>(MachineLimitsUsage::TimeEstimateOnly)); { struct AxisDefault { diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index 2243a3c1b..bc68cd377 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -3,7 +3,6 @@ #include <libslic3r/SLA/RasterBase.hpp> #include "libslic3r/ExPolygon.hpp" -#include "libslic3r/MTUtils.hpp" // For rasterizing #include <agg/agg_basics.h> diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index 1e5de5158..b92862439 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -47,7 +47,7 @@ inline indexed_triangle_set straight_walls(const Polygon &plate, double lo_z, double hi_z) { - return walls(plate, plate, lo_z, hi_z); + return wall_strip(plate, hi_z, lo_z); //walls(plate, plate, lo_z, hi_z); } // Function to cut tiny connector cavities for a given polygon. The input poly diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index 581e84880..cc9aca027 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -77,6 +77,8 @@ std::unique_ptr<RasterBase> create_raster_grayscale_aa( if (gamma > 0) rst = std::make_unique<RasterGrayscaleAAGammaPower>(res, pxdim, tr, gamma); + else if (std::abs(gamma - 1.) < 1e-6) + rst = std::make_unique<RasterGrayscaleAA>(res, pxdim, tr, agg::gamma_none()); else rst = std::make_unique<RasterGrayscaleAA>(res, pxdim, tr, agg::gamma_threshold(.5)); diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 1eba360e3..6439830fe 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -9,7 +9,7 @@ #include <cstdint> #include <libslic3r/ExPolygon.hpp> -#include <libslic3r/SLA/Concurrency.hpp> +//#include <libslic3r/SLA/Concurrency.hpp> namespace Slic3r { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 833146985..0622bec4e 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/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp index 3b55cf066..969fa8dac 100644 --- a/src/libslic3r/SlicesToTriangleMesh.cpp +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -2,7 +2,6 @@ #include "SlicesToTriangleMesh.hpp" -//#include "libslic3r/MTUtils.hpp" #include "libslic3r/Execution/ExecutionTBB.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Tesselate.hpp" @@ -12,39 +11,6 @@ namespace Slic3r { -inline indexed_triangle_set wall_strip(const Polygon &poly, - double lower_z_mm, - double upper_z_mm) -{ - indexed_triangle_set ret; - - size_t startidx = ret.vertices.size(); - size_t offs = poly.points.size(); - - ret.vertices.reserve(ret.vertices.size() + 2 *offs); - - // The expression unscaled(p).cast<float>().eval() is important here - // as it ensures identical conversion of 2D scaled coordinates to float 3D - // to that used by the tesselation. This way, the duplicated vertices in the - // output mesh can be found with the == operator of the points. - // its_merge_vertices will then reliably remove the duplicates. - for (const Point &p : poly.points) - ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(lower_z_mm))); - - for (const Point &p : poly.points) - ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(upper_z_mm))); - - for (size_t i = startidx + 1; i < startidx + offs; ++i) { - ret.indices.emplace_back(i - 1, i, i + offs - 1); - ret.indices.emplace_back(i, i + offs, i + offs - 1); - } - - ret.indices.emplace_back(startidx + offs - 1, startidx, startidx + 2 * offs - 1); - ret.indices.emplace_back(startidx, startidx + offs, startidx + 2 * offs - 1); - - return ret; -} - // Same as walls() but with identical higher and lower polygons. indexed_triangle_set inline straight_walls(const Polygon &plate, double lo_z, diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 6a26eb7e2..f82e5edcf 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -36,41 +36,28 @@ #define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS 1 -//==================== -// 2.4.0.beta1 techs -//==================== -#define ENABLE_2_4_0_BETA1 1 +//================ +// 2.4.1.rc techs +//================ +#define ENABLE_2_4_1_RC 1 -// Enable rendering modifiers and similar objects always as transparent -#define ENABLE_MODIFIERS_ALWAYS_TRANSPARENT (1 && ENABLE_2_4_0_BETA1) +// Enable detection of layers for spiral vase prints +#define ENABLE_SPIRAL_VASE_LAYERS (1 && ENABLE_2_4_1_RC) //==================== -// 2.4.0.beta2 techs +// 2.5.0.alpha1 techs //==================== -#define ENABLE_2_4_0_BETA2 1 - -// Enable modified ImGuiWrapper::slider_float() to create a compound widget where -// an additional button can be used to set the keyboard focus into the slider -// to allow the user to type in the desired value -#define ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT (1 && ENABLE_2_4_0_BETA2) -// Enable fit print volume command for circular printbeds -#define ENABLE_ENHANCED_PRINT_VOLUME_FIT (1 && ENABLE_2_4_0_BETA2) - - -//================= -// 2.4.0.rc techs -//================= -#define ENABLE_2_4_0_RC 1 +#define ENABLE_2_5_0_ALPHA1 1 // Enable changes in preview layout -#define ENABLE_PREVIEW_LAYOUT (1 && ENABLE_2_4_0_RC) +#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_4_0_RC) +#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_4_0_RC) +#define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/Tesselate.cpp b/src/libslic3r/Tesselate.cpp index febb3d0e7..aef512f36 100644 --- a/src/libslic3r/Tesselate.cpp +++ b/src/libslic3r/Tesselate.cpp @@ -245,4 +245,35 @@ std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip) return out; } +indexed_triangle_set wall_strip(const Polygon &poly, double lower_z_mm, double upper_z_mm) +{ + indexed_triangle_set ret; + + size_t startidx = ret.vertices.size(); + size_t offs = poly.points.size(); + + ret.vertices.reserve(ret.vertices.size() + 2 *offs); + + // The expression unscaled(p).cast<float>().eval() is important here + // as it ensures identical conversion of 2D scaled coordinates to float 3D + // to that used by the tesselation. This way, the duplicated vertices in the + // output mesh can be found with the == operator of the points. + // its_merge_vertices will then reliably remove the duplicates. + for (const Point &p : poly.points) + ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(lower_z_mm))); + + for (const Point &p : poly.points) + ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(upper_z_mm))); + + for (size_t i = startidx + 1; i < startidx + offs; ++i) { + ret.indices.emplace_back(i - 1, i, i + offs - 1); + ret.indices.emplace_back(i, i + offs, i + offs - 1); + } + + ret.indices.emplace_back(startidx + offs - 1, startidx, startidx + 2 * offs - 1); + ret.indices.emplace_back(startidx, startidx + offs, startidx + 2 * offs - 1); + + return ret; +} + } // namespace Slic3r diff --git a/src/libslic3r/Tesselate.hpp b/src/libslic3r/Tesselate.hpp index 2dbe6caa1..b164b584d 100644 --- a/src/libslic3r/Tesselate.hpp +++ b/src/libslic3r/Tesselate.hpp @@ -2,14 +2,12 @@ #define slic3r_Tesselate_hpp_ #include <vector> +#include <admesh/stl.h> -#include "Point.hpp" +#include "ExPolygon.hpp" namespace Slic3r { -class ExPolygon; -typedef std::vector<ExPolygon> ExPolygons; - const bool constexpr NORMALS_UP = false; const bool constexpr NORMALS_DOWN = true; @@ -20,6 +18,10 @@ extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, boo extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon &poly, bool flip = NORMALS_UP); extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = NORMALS_UP); +indexed_triangle_set wall_strip(const Polygon &poly, + double lower_z_mm, + double upper_z_mm); + } // namespace Slic3r #endif /* slic3r_Tesselate_hpp_ */ diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 9ed12a607..2562d1913 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -75,6 +75,10 @@ extern local_encoded_string encode_path(const char *src); extern std::string decode_path(const char *src); extern std::string normalize_utf8_nfc(const char *src); +// Returns next utf8 sequence length. =number of bytes in string, that creates together one utf-8 character. +// Starting at pos. ASCII characters returns 1. Works also if pos is in the middle of the sequence. +extern size_t get_utf8_sequence_length(const std::string& text, size_t pos = 0); + // Safely rename a file even if the target exists. // On Windows, the file explorer (or anti-virus or whatever else) often locks the file // for a short while, so the file may not be movable. Retry while we see recoverable errors. diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 7e4b281fd..c74e57e99 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -863,6 +863,71 @@ std::string normalize_utf8_nfc(const char *src) return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); } +size_t get_utf8_sequence_length(const std::string& text, size_t pos) +{ + assert(pos < text.size()); + size_t length = 0; + unsigned char c = text[pos]; + if (c < 0x80) { // 0x00-0x7F + // is ASCII letter + length++; + } + // Bytes 0x80 to 0xBD are trailer bytes in a multibyte sequence. + // pos is in the middle of a utf-8 sequence. Add the utf-8 trailer bytes. + else if (c < 0xC0) { // 0x80-0xBF + length++; + while (pos + length < text.size()) { + c = text[pos + length]; + if (c < 0x80 || c >= 0xC0) { + break; // prevent overrun + } + length++; // add a utf-8 trailer byte + } + } + // Bytes 0xC0 to 0xFD are header bytes in a multibyte sequence. + // The number of one bits above the topmost zero bit indicates the number of bytes (including this one) in the whole sequence. + else if (c < 0xE0) { // 0xC0-0xDF + // add a utf-8 sequence (2 bytes) + if (pos + 2 > text.size()) { + return text.size() - pos; // prevent overrun + } + length += 2; + } + else if (c < 0xF0) { // 0xE0-0xEF + // add a utf-8 sequence (3 bytes) + if (pos + 3 > text.size()) { + return text.size() - pos; // prevent overrun + } + length += 3; + } + else if (c < 0xF8) { // 0xF0-0xF7 + // add a utf-8 sequence (4 bytes) + if (pos + 4 > text.size()) { + return text.size() - pos; // prevent overrun + } + length += 4; + } + else if (c < 0xFC) { // 0xF8-0xFB + // add a utf-8 sequence (5 bytes) + if (pos + 5 > text.size()) { + return text.size() - pos; // prevent overrun + } + length += 5; + } + else if (c < 0xFE) { // 0xFC-0xFD + // add a utf-8 sequence (6 bytes) + if (pos + 6 > text.size()) { + return text.size() - pos; // prevent overrun + } + length += 6; + } + else { // 0xFE-0xFF + // not a utf-8 sequence + length++; + } + return length; +} + namespace PerlUtils { // Get a file name including the extension. std::string path_to_filename(const char *src) { return boost::filesystem::path(src).filename().string(); } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 211a2c2e7..34c0efd01 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 diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index bd5ca9f52..21e23d402 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -19,8 +19,10 @@ #include <boost/log/trivial.hpp> static const float GROUND_Z = -0.02f; -static const std::array<float, 4> DEFAULT_MODEL_COLOR = { 0.235f, 0.235f, 0.235f, 1.0f }; -static const std::array<float, 4> 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<GLModel*>(&m_arrow)->set_color(-1, { 0.75f, 0.0f, 0.0f, 1.0f }); + const_cast<GLModel*>(&m_arrow)->set_color(-1, ColorRGBA::X()); render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast<float>()); // y axis - const_cast<GLModel*>(&m_arrow)->set_color(-1, { 0.0f, 0.75f, 0.0f, 1.0f }); + const_cast<GLModel*>(&m_arrow)->set_color(-1, ColorRGBA::Y()); render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast<float>()); // z axis - const_cast<GLModel*>(&m_arrow)->set_color(-1, { 0.0f, 0.0f, 0.75f, 1.0f }); + const_cast<GLModel*>(&m_arrow)->set_color(-1, ColorRGBA::Z()); render_axis(Geometry::assemble_transform(m_origin).cast<float>()); 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 94b1f3156..93f0548bc 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<float, 4> GLVolume::SELECTED_COLOR = { 0.0f, 1.0f, 0.0f, 1.0f }; -const std::array<float, 4> GLVolume::HOVER_SELECT_COLOR = { 0.4f, 0.9f, 0.1f, 1.0f }; -const std::array<float, 4> GLVolume::HOVER_DESELECT_COLOR = { 1.0f, 0.75f, 0.75f, 1.0f }; -const std::array<float, 4> GLVolume::OUTSIDE_COLOR = { 0.0f, 0.38f, 0.8f, 1.0f }; -const std::array<float, 4> GLVolume::SELECTED_OUTSIDE_COLOR = { 0.19f, 0.58f, 1.0f, 1.0f }; -const std::array<float, 4> GLVolume::DISABLED_COLOR = { 0.25f, 0.25f, 0.25f, 1.0f }; -const std::array<float, 4> GLVolume::SLA_SUPPORT_COLOR = { 0.75f, 0.75f, 0.75f, 1.0f }; -const std::array<float, 4> GLVolume::SLA_PAD_COLOR = { 0.0f, 0.2f, 0.0f, 1.0f }; -const std::array<float, 4> GLVolume::NEUTRAL_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f }; -const std::array<std::array<float, 4>, 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<ColorRGBA, 4> 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<float, 4>& 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<float, 4>& rgba) -{ - render_color = rgba; -} - void GLVolume::set_render_color() { bool outside = is_outside || is_below_printbed(); @@ -432,46 +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<float, 4> color_from_model_volume(const ModelVolume& model_volume) +ColorRGBA color_from_model_volume(const ModelVolume& model_volume) { - std::array<float, 4> 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()) { -#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT - color[0] = 1.0f; - color[1] = 1.0f; - color[2] = 0.2f; -#else - color[0] = 0.2f; - color[1] = 1.0f; - color[2] = 0.2f; -#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT - } - 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; } @@ -631,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<float, 4> 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)); @@ -713,13 +680,13 @@ int GLVolumeCollection::load_wipe_tower_preview( height = 0.1f; TriangleMesh mesh; - std::array<float, 4> 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); @@ -775,14 +742,14 @@ int GLVolumeCollection::load_wipe_tower_preview( return int(volumes.size() - 1); } -GLVolume* GLVolumeCollection::new_toolpath_volume(const std::array<float, 4>& 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<float, 4>& 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; @@ -799,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) && @@ -845,15 +812,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_CULL_FACE)); for (GLVolumeWithIdAndZ& volume : to_render) { -#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT if (type == ERenderType::Transparent) volume.first->force_transparent = true; -#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT volume.first->set_render_color(); -#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT if (type == ERenderType::Transparent) volume.first->force_transparent = false; -#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT // render sinking contours of non-hovered volumes if (m_show_sinking_contours) @@ -982,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; } @@ -991,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::string, ColorRGBA>; + std::vector<ColorItem> 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<Color> colors; - - if (static_cast<PrinterTechnology>(config->opt_int("printer_technology")) == ptSLA) - { + if (static_cast<PrinterTechnology>(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<ConfigOptionString>()->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<const ConfigOptionStrings*>(config->option("extruder_colour")); if (extruders_opt == nullptr) return; @@ -1039,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 6d82e3bb7..7ad12c354 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<float, 4> 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<float, 4> SELECTED_COLOR; - static const std::array<float, 4> HOVER_SELECT_COLOR; - static const std::array<float, 4> HOVER_DESELECT_COLOR; - static const std::array<float, 4> OUTSIDE_COLOR; - static const std::array<float, 4> SELECTED_OUTSIDE_COLOR; - static const std::array<float, 4> DISABLED_COLOR; - static const std::array<float, 4> SLA_SUPPORT_COLOR; - static const std::array<float, 4> SLA_PAD_COLOR; - static const std::array<float, 4> NEUTRAL_COLOR; - static const std::array<std::array<float, 4>, 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<ColorRGBA, 4> 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<float, 4>& 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<float, 4> color; + ColorRGBA color; // Color used to render this volume. - std::array<float, 4> 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<float, 4>& rgba); - void set_render_color(float r, float g, float b, float a); - void set_render_color(const std::array<float, 4>& 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<float, 4>& rgba, size_t reserve_vbo_floats = 0); - GLVolume* new_nontoolpath_volume(const std::array<float, 4>& 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<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const; diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 05f301186..e444fb03c 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" @@ -132,13 +133,13 @@ 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();// wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - 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 = wxGetApp().get_label_clr_default(); + 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( "<html>" @@ -256,9 +257,9 @@ 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();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - 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 = wxGetApp().get_label_clr_default(); + 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/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index e93f32b03..5f7b4e8d3 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -81,7 +81,7 @@ std::pair<std::string, bool> SlicingProcessCompletedEvent::format_error_message( "be glad if you reported it."))) % SLIC3R_APP_NAME).str()); error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what()); } catch (const HardCrash &ex) { - error = GUI::format("PrusaSlicer has encountered a fatal error: \"%1%\"", ex.what()) + "\n\n" + + error = GUI::format(_L("PrusaSlicer has encountered a fatal error: \"%1%\""), ex.what()) + "\n\n" + _u8L("Please save your project and restart PrusaSlicer. " "We would be glad if you reported the issue."); } catch (PlaceholderParserError &ex) { diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 246c8b63e..27caedfc3 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -309,7 +309,7 @@ wxPanel* BedShapePanel::init_texture_panel() wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject()); if (lbl != nullptr) { bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture); - lbl->SetForegroundColour(exists ? /*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/wxGetApp().get_label_clr_default() : wxColor(*wxRED)); + lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED)); wxString tooltip_text = ""; if (m_custom_texture != NONE) { @@ -380,7 +380,7 @@ wxPanel* BedShapePanel::init_model_panel() wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject()); if (lbl != nullptr) { bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model); - lbl->SetForegroundColour(exists ? /*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/wxGetApp().get_label_clr_default() : wxColor(*wxRED)); + lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED)); wxString tooltip_text = ""; if (m_custom_model != NONE) { diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 39ba849d3..e23591fb6 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 4d1d383c4..5af90c5f7 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -9,9 +9,12 @@ #include <wx/wx.h> #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<std::string, wxBitmap*> 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 7d2029e22..e2bea55d1 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. @@ -105,7 +104,7 @@ static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const wxString text = "<html>" "<body bgcolor=\"" + get_color(wxGetApp().get_window_default_clr()/*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)*/) + "\" cellspacing=\"2\" cellpadding=\"0\" border=\"0\" link=\"#800000\">" - "<font color=\"" + get_color(wxGetApp().get_label_clr_default()/*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/) + "\">"; + "<font color=\"" + get_color(wxGetApp().get_label_clr_default()) + "\">"; text += "<table style=\"width:100%\">"; for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) { const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1]; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index bcc4ff3fa..4ff089882 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<std::s wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); #endif #endif - const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); - const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); + const auto text_clr = wxGetApp().get_label_clr_default(); + const auto bgr_clr_str = encode_color(ColorRGB(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())); wxString first_line = format_wxstr(_L("%1% marked with <b>*</b> are <b>not</b> compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); wxString text; if (all_printers) { @@ -2729,8 +2730,11 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese return false; } else { - bool is_filaments_changed = app_config->get_section(AppConfig::SECTION_FILAMENTS) != appconfig_new.get_section(AppConfig::SECTION_FILAMENTS); - bool is_sla_materials_changed = app_config->get_section(AppConfig::SECTION_MATERIALS) != appconfig_new.get_section(AppConfig::SECTION_MATERIALS); + auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) { + return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>()) != appconfig_new.get_section(section_name); + }; + bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS); + bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS); if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled."); if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 3d914d5b6..ee5aacb19 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<std::string>& colors = ColorPrintColors::get(); if (ticks.empty()) diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 23275cf2a..e0f713d87 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 <wx/window.h> #include <wx/control.h> @@ -119,7 +118,6 @@ class TickCodeInfo // int m_default_color_idx = 0; std::vector<std::string>* 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 b5955f2fc..283d527fa 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 <stdio.h> #include <random> -#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<double> 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<double>(hsv_scnd.h, hsv_frst.h) : std::max<double>(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 5ec622b87..172a07f66 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1389,10 +1389,8 @@ boost::any& ColourPicker::get_value() auto colour = static_cast<wxColourPickerCtrl*>(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(); - } + else + m_value = encode_color(ColorRGB(colour.Red(), colour.Green(), colour.Blue())); return m_value; } diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 4966726ae..7600ef4fa 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -270,13 +270,12 @@ void FirmwareDialog::priv::flashing_start(unsigned tasks) void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) { - auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); port_picker->Enable(); btn_rescan->Enable(); hex_picker->Enable(); btn_close->Enable(); btn_flash->SetLabel(btn_flash_label_ready); - txt_status->SetForegroundColour(text_color); + txt_status->SetForegroundColour(GUI::wxGetApp().get_label_clr_default()); timer_pulse.Stop(); progressbar->SetValue(progressbar->GetRange()); @@ -811,7 +810,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : panel->SetSizer(vsizer); auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); - p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr, + p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, /*wxFileSelectorPromptStr*/_L("Select a file"), "Hex files (*.hex)|*.hex|All files|*.*"); p->hex_picker->GetPickerCtrl()->SetLabelText(_(L("Browse"))); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3d16c7244..5edc321f7 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<EMoveType>(static_cast<unsigned char>(EMoveType::Retract) + id); } -static std::array<float, 4> decode_color(const std::string& color) { - static const float INV_255 = 1.0f / 255.0f; - - std::array<float, 4> ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - const char* c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - ret[j] = float(digit1 * 16 + digit2) * INV_255; - } - } - return ret; -} - -static std::vector<std::array<float, 4>> decode_colors(const std::vector<std::string>& colors) { - std::vector<std::array<float, 4>> output(colors.size(), { 0.0f, 0.0f, 0.0f, 1.0f }); - for (size_t i = 0; i < colors.size(); ++i) { - output[i] = decode_color(colors[i]); - } - return output; -} - // Round to a bin with minimum two digits resolution. // Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. static float round_to_bin(const float value) @@ -197,9 +171,9 @@ float GCodeViewer::Extrusions::Range::step_size(EType type) const } } -GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const #else -GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const #endif // ENABLE_PREVIEW_LAYER_TIME { // Input value scaled to the colors range @@ -222,18 +196,11 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con const size_t color_max_idx = Range_Colors.size() - 1; // Compute the two colors just below (low) and above (high) the input value - const size_t color_low_idx = std::clamp<size_t>(static_cast<size_t>(global_t), 0, color_max_idx); + const size_t color_low_idx = std::clamp<size_t>(static_cast<size_t>(global_t), 0, color_max_idx); const size_t color_high_idx = std::clamp<size_t>(color_low_idx + 1, 0, color_max_idx); - // Compute how far the value is between the low and high colors so that they can be interpolated - const float local_t = std::clamp(global_t - static_cast<float>(color_low_idx), 0.0f, 1.0f); - // Interpolate between the low and high colors to find exactly which color the input value should get - Color ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - for (unsigned int i = 0; i < 3; ++i) { - ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); - } - return ret; + return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast<float>(color_low_idx)); } GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { @@ -311,12 +278,7 @@ void GCodeViewer::SequentialView::Marker::render() const if (width != last_window_width || length != last_text_length) { last_window_width = width; last_text_length = length; -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT imgui.set_requires_extra_frame(); -#else - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT } imgui.end(); @@ -383,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; @@ -512,7 +474,7 @@ void GCodeViewer::SequentialView::render(float legend_height) const gcode_window.render(legend_height, bottom, static_cast<uint64_t>(gcode_ids[current.last])); } -const std::vector<GCodeViewer::Color> GCodeViewer::Extrusion_Role_Colors {{ +const std::vector<ColorRGBA> 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 @@ -531,7 +493,7 @@ const std::vector<GCodeViewer::Color> GCodeViewer::Extrusion_Role_Colors {{ { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed }}; -const std::vector<GCodeViewer::Color> GCodeViewer::Options_Colors {{ +const std::vector<ColorRGBA> 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 @@ -541,7 +503,7 @@ const std::vector<GCodeViewer::Color> GCodeViewer::Options_Colors {{ { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes }}; -const std::vector<GCodeViewer::Color> GCodeViewer::Travel_Colors {{ +const std::vector<ColorRGBA> 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 @@ -549,7 +511,7 @@ const std::vector<GCodeViewer::Color> GCodeViewer::Travel_Colors {{ #if 1 // Normal ranges -const std::vector<GCodeViewer::Color> GCodeViewer::Range_Colors {{ +const std::vector<ColorRGBA> 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 }, @@ -564,7 +526,7 @@ const std::vector<GCodeViewer::Color> GCodeViewer::Range_Colors {{ }}; #else // Detailed ranges -const std::vector<GCodeViewer::Color> GCodeViewer::Range_Colors{ { +const std::vector<ColorRGBA> 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 }, @@ -589,8 +551,8 @@ const std::vector<GCodeViewer::Color> 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() { @@ -761,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(); @@ -850,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<Color>(); + m_tool_colors = std::vector<ColorRGBA>(); m_extruders_count = 0; m_extruder_ids = std::vector<unsigned char>(); m_filament_diameters = std::vector<float>(); @@ -1040,7 +1005,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const return; // collect color information to generate materials - std::vector<Color> colors; + std::vector<ColorRGBA> colors; for (const RenderPath& path : t_buffer.render_paths) { colors.push_back(path.color); } @@ -1062,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"); } @@ -1126,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); @@ -2097,6 +2062,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) sort_remove_duplicates(m_extruder_ids); m_extruder_ids.shrink_to_fit(); +#if ENABLE_SPIRAL_VASE_LAYERS + // replace layers for spiral vase mode + if (!gcode_result.spiral_vase_layers.empty()) { + m_layers.reset(); + for (const auto& layer : gcode_result.spiral_vase_layers) { + m_layers.append(layer.first, { layer.second.first, layer.second.second }); + } + } +#endif // ENABLE_SPIRAL_VASE_LAYERS + // set layers z range if (!m_layers.empty()) m_layers_z_range = { 0, static_cast<unsigned int>(m_layers.size() - 1) }; @@ -2178,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(); } @@ -2191,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<unsigned int>(path.role)]; break; } @@ -2224,13 +2199,13 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { if (path.cp_color_id >= static_cast<unsigned char>(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; @@ -2429,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: @@ -3081,7 +3056,7 @@ void GCodeViewer::render_legend(float& legend_height) 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<float, 4>& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, double used_filament_m = 0.0, double used_filament_g = 0.0, std::function<void()> callback = nullptr) { @@ -3094,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; } } @@ -3133,12 +3108,7 @@ void GCodeViewer::render_legend(float& legend_height) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); // to avoid the tooltip to change size when moving the mouse -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT imgui.set_requires_extra_frame(); -#else - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT } } @@ -3283,7 +3253,7 @@ void GCodeViewer::render_legend(float& legend_height) }; auto color_print_ranges = [this](unsigned char extruder_id, const std::vector<CustomGCode::Item>& custom_gcode_per_print_z) { - std::vector<std::pair<Color, std::pair<double, double>>> ret; + std::vector<std::pair<ColorRGBA, std::pair<double, double>>> ret; ret.reserve(custom_gcode_per_print_z.size()); for (const auto& item : custom_gcode_per_print_z) { @@ -3302,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; @@ -3562,11 +3535,10 @@ void GCodeViewer::render_legend(float& legend_height) if (need_scrollable) ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); if (m_extruders_count == 1) { // single extruder use case - const std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const std::vector<std::pair<ColorRGBA, std::pair<double, double>>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast<int>(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode + 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 @@ -3585,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<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const std::vector<std::pair<ColorRGBA, std::pair<double, double>>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); const int items_cnt = static_cast<int>(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode + 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 @@ -3638,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<double, double> used_filament {0.0f, 0.0f}; + std::pair<double, double> used_filament{ 0.0f, 0.0f }; }; using PartialTimes = std::vector<PartialTime>; @@ -3650,7 +3622,7 @@ void GCodeViewer::render_legend(float& legend_height) std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; int extruders_count = wxGetApp().extruders_edited_cnt(); - std::vector<Color> last_color(extruders_count); + std::vector<ColorRGBA> last_color(extruders_count); for (int i = 0; i < extruders_count; ++i) { last_color[i] = m_tool_colors[i]; } @@ -3662,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; @@ -3671,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; } @@ -3689,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<float, 4>& offsets, const Times& times) { + auto append_color_change = [&imgui](const ColorRGBA& color1, const ColorRGBA& color2, const std::array<float, 4>& offsets, const Times& times) { imgui.text(_u8L("Color change")); ImGui::SameLine(); @@ -3699,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<float, 4>& offsets, const Times& times, std::pair<double, double> used_filament) { + auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array<float, 4>& offsets, const Times& times, std::pair<double, double> used_filament) { imgui.text(_u8L("Print")); ImGui::SameLine(); @@ -3718,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))); @@ -3994,12 +3968,7 @@ void GCodeViewer::render_legend(float& legend_height) if (m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic) refresh_render_paths(false, false); #endif // ENABLE_PREVIEW_LAYER_TIME -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT imgui.set_requires_extra_frame(); -#else - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT } } }; @@ -4344,7 +4313,7 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) } } -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 d6371df89..f7adcc9eb 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<float, 4>; using VertexBuffer = std::vector<float>; using MultiVertexBuffer = std::vector<VertexBuffer>; using IndexBuffer = std::vector<IBufferType>; @@ -31,12 +30,12 @@ class GCodeViewer using InstanceIdBuffer = std::vector<size_t>; using InstancesOffsets = std::vector<Vec3f>; - static const std::vector<Color> Extrusion_Role_Colors; - static const std::vector<Color> Options_Colors; - static const std::vector<Color> Travel_Colors; - static const std::vector<Color> Range_Colors; - static const Color Wipe_Color; - static const Color Neutral_Color; + static const std::vector<ColorRGBA> Extrusion_Role_Colors; + static const std::vector<ColorRGBA> Options_Colors; + static const std::vector<ColorRGBA> Travel_Colors; + static const std::vector<ColorRGBA> 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<Range> 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; @@ -408,10 +405,10 @@ class GCodeViewer #if ENABLE_PREVIEW_LAYER_TIME float step_size(EType type = EType::Linear) const; - Color get_color_at(float value, 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<float>(Range_Colors.size()) - 1.0f); } - Color get_color_at(float value) const; + ColorRGBA get_color_at(float value) const; #endif // ENABLE_PREVIEW_LAYER_TIME }; @@ -471,6 +468,7 @@ class GCodeViewer size_t last{ 0 }; 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; } }; @@ -500,9 +498,8 @@ class GCodeViewer bool operator != (const Layers& other) const { if (m_zs != other.m_zs) return true; - if (!(m_ranges == other.m_ranges)) + if (m_ranges != other.m_ranges) return true; - return false; } }; @@ -514,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; } @@ -722,7 +719,7 @@ private: // bounding box of toolpaths + marker tools BoundingBoxf3 m_max_bounding_box; float m_max_print_height{ 0.0f }; - std::vector<Color> m_tool_colors; + std::vector<ColorRGBA> m_tool_colors; Layers m_layers; std::array<unsigned int, 2> m_layers_z_range; std::vector<ExtrusionRole> m_roles; @@ -838,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 b03206090..fb22f9099 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 @@ -731,13 +730,8 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_ } // force re-render while the windows gets to its final size (it takes several frames) -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) imgui.set_requires_extra_frame(); -#else - if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) - m_canvas.request_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT imgui.end(); ImGui::PopStyleColor(); @@ -783,13 +777,8 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas ImGui::TextUnformatted(m_text.c_str()); // force re-render while the windows gets to its final size (it may take several frames) or while hidden -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) imgui.set_requires_extra_frame(); -#else - if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) - canvas.request_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT size = ImGui::GetWindowSize(); @@ -859,8 +848,8 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons void GLCanvas3D::SequentialPrintClearance::render() { - std::array<float, 4> FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; - std::array<float, 4> 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) @@ -1126,10 +1115,9 @@ void GLCanvas3D::reset_volumes() ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const { - assert(m_initialized); - - ModelInstanceEPrintVolumeState state; - m_volumes.check_outside_state(m_bed.build_volume(), &state); + ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside; + if (m_initialized) + m_volumes.check_outside_state(m_bed.build_volume(), &state); return state; } @@ -1303,15 +1291,6 @@ bool GLCanvas3D::is_reload_delayed() const void GLCanvas3D::enable_layers_editing(bool enable) { m_layers_editing.set_enabled(enable); -#if !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT - const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); - for (unsigned int idx : idxs) { - GLVolume* v = m_volumes.volumes[idx]; - if (v->is_modifier) - v->force_transparent = enable; - } -#endif // !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT - set_as_dirty(); } @@ -2263,29 +2242,20 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current(); if (gizmo != nullptr) m_dirty |= gizmo->update_items_state(); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT // ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame(); m_dirty |= imgui_requires_extra_frame; -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT if (!m_dirty) return; -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT // this needs to be done here. // during the render launched by the refresh the value may be set again wxGetApp().imgui()->reset_requires_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT _refresh_if_shown_on_screen(); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) { -#else - if (m_extra_frame_requested || mouse3d_controller_applied) { - m_dirty = true; -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_extra_frame_requested = false; evt.RequestMore(); } @@ -4144,9 +4114,6 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const return ret; }; - static const std::array<float, 4> orange = { 0.923f, 0.504f, 0.264f, 1.0f }; - static const std::array<float, 4> gray = { 0.64f, 0.64f, 0.64f, 1.0f }; - GLVolumePtrs visible_volumes; for (GLVolume* vol : volumes.volumes) { @@ -4201,7 +4168,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; @@ -4932,19 +4899,20 @@ void GLCanvas3D::_picking_pass() int volume_id = -1; int gizmo_id = -1; - GLubyte color[4] = { 0, 0, 0, 0 }; + std::array<GLubyte, 4> 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()) { @@ -5060,19 +5028,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()); @@ -5355,8 +5315,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)); @@ -5371,13 +5329,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(); } } @@ -5796,7 +5750,7 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) if (!print->has_skirt() && !print->has_brim()) return; - const std::array<float, 4> 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; @@ -5843,7 +5797,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<std::string>& str_tool_colors, const std::vector<CustomGCode::Item>& color_print_values) { - std::vector<std::array<float, 4>> tool_colors = _parse_colors(str_tool_colors); + std::vector<ColorRGBA> tool_colors; + decode_colors(str_tool_colors, tool_colors); struct Ctxt { @@ -5852,20 +5807,20 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool has_perimeters; bool has_infill; bool has_support; - const std::vector<std::array<float, 4>>* tool_colors; + const std::vector<ColorRGBA>* tool_colors; bool is_single_material_print; int extruders_cnt; const std::vector<CustomGCode::Item>* color_print_values; - static const std::array<float, 4>& color_perimeters() { static std::array<float, 4> color = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow - static const std::array<float, 4>& color_infill() { static std::array<float, 4> color = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish - static const std::array<float, 4>& color_support() { static std::array<float, 4> color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - static const std::array<float, 4>& color_pause_or_custom_code() { static std::array<float, 4> 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<float, 4>& 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; } @@ -6005,7 +5960,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<float, 4>& 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; @@ -6146,21 +6101,22 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con if (!print->is_step_done(psWipeTower)) return; - std::vector<std::array<float, 4>> tool_colors = _parse_colors(str_tool_colors); + std::vector<ColorRGBA> tool_colors; + decode_colors(str_tool_colors, tool_colors); struct Ctxt { const Print *print; - const std::vector<std::array<float, 4>>* tool_colors; + const std::vector<ColorRGBA>* tool_colors; Vec2f wipe_tower_pos; float wipe_tower_angle; - static const std::array<float, 4>& color_support() { static std::array<float, 4> 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<float, 4>& 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<int>(this->number_tools() - 1, std::max<int>(tool, 0)) : feature; } @@ -6192,7 +6148,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<float, 4>& 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; @@ -6311,7 +6267,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<float, 4>& 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 @@ -6373,28 +6329,6 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) _set_warning_notification(warning, show); } -std::vector<std::array<float, 4>> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors) -{ - static const float INV_255 = 1.0f / 255.0f; - - std::vector<std::array<float, 4>> 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 0cbd27ed0..ebffe4695 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -990,8 +990,6 @@ private: bool _deactivate_arrange_menu(); float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); } - - static std::vector<std::array<float, 4>> _parse_colors(const std::vector<std::string>& colors); }; } // namespace GUI diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 422b65408..fa804efc2 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<float, 4>& 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<int>(i) == entity_id) diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index d47c56fd9..62722b18c 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 <vector> #include <string> @@ -33,7 +34,7 @@ namespace GUI { unsigned int vbo_id{ 0 }; unsigned int ibo_id{ 0 }; size_t indices_count{ 0 }; - std::array<float, 4> color{ 1.0f, 1.0f, 1.0f, 1.0f }; + ColorRGBA color; }; struct InitializationData @@ -44,7 +45,7 @@ namespace GUI { std::vector<Vec3f> positions; std::vector<Vec3f> normals; std::vector<unsigned int> indices; - std::array<float, 4> color{ 1.0f, 1.0f, 1.0f, 1.0f }; + ColorRGBA color; }; std::vector<Entity> 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<float, 4>& 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 4c36efdd9..32b3d5960 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 <boost/nowide/fstream.hpp> #include <GL/glew.h> @@ -306,6 +307,16 @@ void GLShaderProgram::set_uniform(int id, const Vec3d& value) const set_uniform(id, static_cast<Vec3f>(value.cast<float>())); } +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 { assert(m_id > 0); diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 46e46b4f0..06a5c00e5 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: @@ -60,6 +63,8 @@ public: 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; @@ -77,6 +82,8 @@ public: 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 5ebd5300a..93cd9073a 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" @@ -1143,15 +1144,27 @@ bool GUI_App::on_init_inner() // Detect position (display) to show the splash screen // Now this position is equal to the mainframe position wxPoint splashscreen_pos = wxDefaultPosition; - if (app_config->has("window_mainframe")) { + bool default_splashscreen_pos = true; + if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") { auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); - if (metrics) + default_splashscreen_pos = metrics == boost::none; + if (!default_splashscreen_pos) splashscreen_pos = metrics->get_rect().GetPosition(); } + if (!default_splashscreen_pos) { + // workaround for crash related to the positioning of the window on secondary monitor + get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos"); + get_app_config()->save(); + } + // create splash screen with updated bmp scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); + + if (!default_splashscreen_pos) + // revert "restore_win_position" value if application wasn't crashed + get_app_config()->set("restore_win_position", "1"); #ifndef __linux__ wxYield(); #endif @@ -1308,6 +1321,35 @@ bool GUI_App::on_init_inner() }); m_initialized = true; + + if (const std::string& crash_reason = app_config->get("restore_win_position"); + boost::starts_with(crash_reason,"crashed")) + { + wxString preferences_item = _L("Restore window position on start"); + InfoDialog dialog(nullptr, + _L("PrusaSlicer is started in save mode"), + format_wxstr(_L("PrusaSlicer was crashed last time due to \"%1%\".\n" + "For more information see issues \"%2%\" and \"%3%\"\n\n" + "To avoid an application crash next time you have to disable\n" + "\"%4%\" in \"Preferences\""), + "<b>" + from_u8(crash_reason) + "</b>", + "<a href=http://github.com/prusa3d/PrusaSlicer/issues/2939>#2939</a>", + "<a href=http://github.com/prusa3d/PrusaSlicer/issues/5573>#5573</a>", + "<b>" + preferences_item + "</b>") + + "\n\n" + + format_wxstr(_L("Note: Enabling of the \"%1%\" will caused an application crash on next start."), preferences_item), + true, wxYES_NO); + + dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item)); + dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Enable \"%1%\"") , preferences_item)); + + auto answer = dialog.ShowModal(); + if (answer == wxID_YES) + app_config->set("restore_win_position", "0"); + else if (answer == wxID_NO) + app_config->set("restore_win_position", "1"); + } + return true; } @@ -1388,11 +1430,13 @@ void GUI_App::update_label_colours() tab->update_label_colours(); } +#ifdef _WIN32 static bool is_focused(HWND hWnd) { HWND hFocusedWnd = ::GetFocus(); return hFocusedWnd && hWnd == hFocusedWnd; } +#endif void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) { @@ -1537,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(); } @@ -1548,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(); } @@ -2759,7 +2801,7 @@ wxString GUI_App::current_language_code_safe() const void GUI_App::open_web_page_localized(const std::string &http_address) { - open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe()); + open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); } // If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). @@ -2901,8 +2943,24 @@ void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &na } const wxRect& rect = metrics->get_rect(); - window->SetPosition(rect.GetPosition()); - window->SetSize(rect.GetSize()); + + if (app_config->get("restore_win_position") == "1") { + // workaround for crash related to the positioning of the window on secondary monitor + app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str()); + app_config->save(); + window->SetPosition(rect.GetPosition()); + + // workaround for crash related to the positioning of the window on secondary monitor + app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str()); + app_config->save(); + window->SetSize(rect.GetSize()); + + // revert "restore_win_position" value if application wasn't crashed + app_config->set("restore_win_position", "1"); + } + else + window->CenterOnScreen(); + window->Maximize(metrics->get_maximized()); } @@ -2963,19 +3021,40 @@ void GUI_App::check_updates(const bool verbose) } } -bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* = 0*/) +bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) { bool launch = true; - if (get_app_config()->get("suppress_hyperlinks").empty()) { - RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - dialog.ShowCheckBox(_L("Remember my choice")); - int answer = dialog.ShowModal(); - launch = answer == wxID_YES; - get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : ""); + // warning dialog containes a "Remember my choice" checkbox + std::string option_key = "suppress_hyperlinks"; + if (force_remember_choice || app_config->get(option_key).empty()) { + if (app_config->get(option_key).empty()) { + RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + dialog.ShowCheckBox(_L("Remember my choice")); + auto answer = dialog.ShowModal(); + launch = answer == wxID_YES; + if (dialog.IsCheckBoxChecked()) { + wxString preferences_item = _L("Suppress to open hyperlink in browser"); + wxString msg = + _L("PrusaSlicer will remember your choice.") + "\n\n" + + _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + + format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); + + MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); + if (msg_dlg.ShowModal() == wxID_CANCEL) + return false; + app_config->set(option_key, answer == wxID_NO ? "1" : "0"); + } + } + if (launch) + launch = app_config->get(option_key) != "1"; + } + // warning dialog doesn't containe a "Remember my choice" checkbox + // and will be shown only when "Suppress to open hyperlink in browser" is ON. + else if (app_config->get(option_key) == "1") { + MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + launch = dialog.ShowModal() == wxID_YES; } - if (launch) - launch = get_app_config()->get("suppress_hyperlinks") != "1"; return launch && wxLaunchDefaultBrowser(url, flags); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index eddaf4a97..d3009a0fa 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -268,7 +268,8 @@ public: virtual bool OnExceptionInMainLoop() override; // Calls wxLaunchDefaultBrowser if user confirms in dialog. - bool open_browser_with_warning_dialog(const wxString& url, int flags = 0); + // Add "Rememeber my choice" checkbox to question dialog, when it is forced or a "suppress_hyperlinks" option has empty value + bool open_browser_with_warning_dialog(const wxString& url, wxWindow* parent = nullptr, bool force_remember_choice = true, int flags = 0); #ifdef __APPLE__ void OSXStoreOpenFiles(const wxArrayString &files) override; // wxWidgets override to get an event on open files. diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 04a392169..c3aaa2f4e 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -716,7 +716,7 @@ void MenuFactory::append_menu_item_export_stl(wxMenu* menu) [](wxCommandEvent&) { plater()->export_stl(false, true); }, "", nullptr, []() { const Selection& selection = plater()->canvas3D()->get_selection(); - return selection.is_single_full_instance() || selection.is_single_full_object(); + return selection.is_single_full_instance() || selection.is_single_full_object() || selection.is_single_volume() || selection.is_single_modifier(); }, m_parent); menu->AppendSeparator(); } @@ -792,14 +792,9 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu) void MenuFactory::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu) { -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT append_menu_item(menu, wxID_ANY, _L("Scale to print volume"), _L("Scale the selected object to fit the print volume"), [](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu, []() { return plater()->can_scale_to_print_volume(); }, m_parent); -#else - append_menu_item(menu, wxID_ANY, _L("Scale to print volume"), _L("Scale the selected object to fit the print volume"), - [](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu); -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT } void MenuFactory::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/) diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 92223a767..000199f93 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 <wx/msgdlg.h> diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 201e5d78d..18e58efb8 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -495,7 +495,7 @@ void ObjectManipulation::update_ui_from_settings() // update colors for edit-boxes int axis_id = 0; for (ManipulationEditor* editor : m_editors) { -// editor->SetForegroundColour(m_use_colors ? wxColour(axes_color_text[axis_id]) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); +// editor->SetForegroundColour(m_use_colors ? wxColour(axes_color_text[axis_id]) : wxGetApp().get_label_clr_default()); if (m_use_colors) { editor->SetBackgroundColour(wxColour(axes_color_back[axis_id])); if (wxGetApp().dark_mode()) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 9c8eaed4a..2cac5e356 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -795,7 +795,8 @@ void Preview::update_layers_slider_mode() return false; for (ModelVolume* volume : object->volumes) - if ((volume->config.has("extruder") && + if ((volume->config.has("extruder") && + volume->config.option("extruder")->getInt() != 0 && // extruder isn't default volume->config.option("extruder")->getInt() != extruder) || !volume->mmu_segmentation_facets.empty()) return false; diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index a5ba218b5..d7d3529fe 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 97ab7094c..21b5c13e6 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<float, 4> 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,7 +42,7 @@ 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<float, 4>& render_color, bool picking) const +void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color, bool picking) const { if (!cube.is_initialized()) { // This cannot be done in constructor, OpenGL is not yet @@ -99,11 +93,6 @@ void GLGizmoBase::set_hover_id(int id) } } -void GLGizmoBase::set_highlight_color(const std::array<float, 4>& color) -{ - m_highlight_color = color; -} - void GLGizmoBase::enable_grabber(unsigned int id) { if (id < m_grabbers.size()) @@ -157,22 +146,13 @@ bool GLGizmoBase::update_items_state() return res; }; -std::array<float, 4> 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, 4> { - 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 @@ -200,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<float, 4> 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); } } @@ -239,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 1e2cd93fb..ca7e3f2f7 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<float, 4> DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f }; -static const std::array<float, 4> DEFAULT_DRAG_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; -static const std::array<float, 4> DEFAULT_HIGHLIGHT_COLOR = { 1.0f, 0.38f, 0.0f, 1.0f }; -static const std::array<std::array<float, 4>, 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<float, 4> 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<ColorRGBA, 3> 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<float, 4> color; + ColorRGBA color; bool enabled; bool dragging; @@ -63,7 +60,7 @@ protected: float get_dragging_half_size(float size) const; private: - void render(float size, const std::array<float, 4>& render_color, bool picking) const; + void render(float size, const ColorRGBA& render_color, bool picking) const; GLModel cube; }; @@ -96,9 +93,9 @@ protected: unsigned int m_sprite_id; int m_hover_id; bool m_dragging; - std::array<float, 4> m_base_color; - std::array<float, 4> m_drag_color; - std::array<float, 4> m_highlight_color; + ColorRGBA m_base_color; + ColorRGBA m_drag_color; + ColorRGBA m_highlight_color; mutable std::vector<Grabber> m_grabbers; ImGuiWrapper* m_imgui; bool m_first_input_window_render; @@ -142,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<float, 4>& color); + void set_highlight_color(const ColorRGBA& color) { m_highlight_color = color; } void enable_grabber(unsigned int id); void disable_grabber(unsigned int id); @@ -184,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<float, 4> 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; @@ -199,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 d2c32d488..90e383e87 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<float, 4> 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(); @@ -167,11 +168,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); if (last_h != win_h || last_y != y) { // ask canvas for another frame to render the window in the correct position -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_imgui->set_requires_extra_frame(); -#else - m_parent.request_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT if (last_h != win_h) last_h = win_h; if (last_y != y) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 4007f89d4..44291a409 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<float, 4> GrabberColor; double m_cut_z{ 0.0 }; double m_max_z{ 0.0 }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index cb8ccf282..d79d193d9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -134,12 +134,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l caption_max += m_imgui->scaled(1.f); const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT const float slider_icon_width = m_imgui->get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; -#else - float window_width = minimal_slider_width + sliders_left_width; -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); window_width = std::max(window_width, split_triangles_checkbox_width); @@ -174,15 +170,10 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height); ImGui::SetCursorPosY(slider_start_position_y); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]); if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) { -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data())) { -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); if (! m_parent.is_using_slope()) { m_parent.use_slope(true); @@ -194,11 +185,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y)); const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; -#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT - if (ImGui::IsItemHovered()) - m_imgui->tooltip(format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " - "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]), max_tooltip_width); -#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); ImGui::NewLine(); @@ -280,15 +266,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_left_width); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); @@ -302,22 +281,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_imgui->text(m_desc["smart_fill_angle"] + ":"); ImGui::SameLine(sliders_left_width); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data())) -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } - -#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); -#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT } ImGui::Separator(); @@ -335,18 +304,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l auto clp_dist = float(m_c->object_clipper()->get_position()); ImGui::SameLine(sliders_left_width); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) m_c->object_clipper()->set_position(clp_dist, true); -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 9034d78d8..fd32c68fc 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(); } @@ -150,9 +148,9 @@ void GLGizmoFlatten::update_planes() std::vector<bool> facet_visited(num_of_facets, false); int facet_queue_cnt = 0; const stl_normal* normal_ptr = nullptr; + int facet_idx = 0; while (1) { // Find next unvisited triangle: - int facet_idx = 0; for (; facet_idx < num_of_facets; ++ facet_idx) if (!facet_visited[facet_idx]) { facet_queue[facet_queue_cnt ++] = facet_idx; @@ -249,7 +247,8 @@ void GLGizmoFlatten::update_planes() } if (discard) { - m_planes.erase(m_planes.begin() + (polygon_id--)); + m_planes[polygon_id--] = std::move(m_planes.back()); + m_planes.pop_back(); continue; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index a001d5a81..16e3730b2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -116,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<float, 4> 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(); @@ -128,26 +128,18 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons continue; // First decide about the color of the point. - if (picking) { - std::array<float, 4> 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<GLModel*>(&m_cylinder)->set_color(-1, render_color); @@ -546,18 +538,11 @@ RENDER_AGAIN: } m_imgui->disabled_begin(! m_enable_hollowing); - float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("offset")); ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); ImGui::PushItemWidth(window_width - settings_sliders_left); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip)); -#else - m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm"); - if (m_imgui->get_last_slider_status().hovered) - m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider @@ -567,13 +552,7 @@ RENDER_AGAIN: ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("quality")); ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip)); -#else - m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f"); - if (m_imgui->get_last_slider_status().hovered) - m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT slider_clicked |= m_imgui->get_last_slider_status().clicked; slider_edited |= m_imgui->get_last_slider_status().edited; @@ -584,13 +563,7 @@ RENDER_AGAIN: ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("closing_distance")); ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip)); -#else - m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); - if (m_imgui->get_last_slider_status().hovered) - m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT slider_clicked |= m_imgui->get_last_slider_status().clicked; slider_edited |= m_imgui->get_last_slider_status().edited; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 285089357..f25a65020 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<std::array<float, 4>> get_extruders_colors() +static std::vector<ColorRGBA> get_extruders_colors() { - unsigned char rgb_color[3] = {}; - std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - std::vector<std::array<float, 4>> 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<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + std::vector<ColorRGBA> ret; + decode_colors(colors, ret); + return ret; } static std::vector<std::string> 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<std::string> &extruders, - const std::vector<std::array<float, 4>> &extruders_colors, - size_t &selection_idx) +static void render_extruders_combo(const std::string& label, + const std::vector<std::string>& extruders, + const std::vector<ColorRGBA>& extruders_colors, + size_t& selection_idx) { assert(!extruders_colors.empty()); assert(extruders_colors.size() == extruders_colors.size()); - auto convert_to_imu32 = [](const std::array<float, 4> &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)); @@ -312,12 +302,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott caption_max += m_imgui->scaled(1.f); const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT const float slider_icon_width = m_imgui->get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; -#else - float window_width = minimal_slider_width + sliders_left_width; -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); window_width = std::max(window_width, split_triangles_checkbox_width); @@ -343,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<float, 4> &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")); @@ -355,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<float, 4> &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; @@ -445,15 +431,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_left_width); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); @@ -469,23 +448,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - if(m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data())) -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } -#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); -#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT - ImGui::Separator(); } @@ -500,18 +469,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott auto clp_dist = float(m_c->object_clipper()->get_position()); ImGui::SameLine(sliders_left_width); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) m_c->object_clipper()->set_position(clp_dist, true); -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { @@ -595,16 +555,18 @@ PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const return PainterGizmoType::MMU_SEGMENTATION; } -std::array<float, 4> GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const +ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const { - const std::array<float, 4> &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<float, 4> GLGizmoMmuSegmentation::get_cursor_sphere_right_button_color() const +ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_right_button_color() const { - const std::array<float, 4> &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 577db8ed5..70bb5241d 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<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color) + TriangleSelectorMmGui(const TriangleMesh& mesh, const std::vector<ColorRGBA>& 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<std::array<float, 4>> &m_colors; - const std::array<float, 4> m_default_volume_color; + const std::vector<ColorRGBA>& 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<float, 4> get_cursor_sphere_left_button_color() const override; - std::array<float, 4> 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<std::string> m_original_extruders_names; - std::vector<std::array<float, 4>> m_original_extruders_colors; - std::vector<std::array<float, 4>> m_modified_extruders_colors; + std::vector<ColorRGBA> m_original_extruders_colors; + std::vector<ColorRGBA> m_modified_extruders_colors; std::vector<int> 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 d16f0ff2d..ff6a05847 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -195,13 +195,9 @@ 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<float, 4> 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) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index fa59d7646..1f654dcc0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -207,7 +207,7 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const if (is_left_handed) glFrontFace(GL_CW); - std::array<float, 4> 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) @@ -709,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<float, 4> TriangleSelectorGUI::get_seed_fill_color(const std::array<float, 4> &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<float, 4> enforcers_color{0.47f, 0.47f, 1.f, 1.f}; - static constexpr std::array<float, 4> 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(); @@ -741,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<float, 4> &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 ea6760a17..53c0f31b1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -86,7 +86,7 @@ public: protected: bool m_update_render_data = false; - static std::array<float, 4> get_seed_fill_color(const std::array<float, 4> &base_color); + static ColorRGBA get_seed_fill_color(const ColorRGBA& base_color); private: void update_render_data(); @@ -135,8 +135,8 @@ protected: virtual void update_model_object() const = 0; virtual void update_from_model_object() = 0; - virtual std::array<float, 4> get_cursor_sphere_left_button_color() const { return {0.f, 0.f, 1.f, 0.25f}; } - virtual std::array<float, 4> 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; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index a234a19ff..5046d1922 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -314,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<float, 4> 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) @@ -540,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<RotoptimizeJob>()); } imgui->disabled_end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index af1ecf548..448efd98a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -2,7 +2,6 @@ #define slic3r_GLGizmoRotate_hpp_ #include "GLGizmoBase.hpp" -#include "../Jobs/RotoptimizeJob.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index b11a8bffd..00921aa9c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -105,12 +105,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) caption_max += m_imgui->scaled(1.f); const float sliders_left_width = std::max(cursor_size_slider_left, clipping_slider_left); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT const float slider_icon_width = m_imgui->get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; -#else - float window_width = minimal_slider_width + sliders_left_width; -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_sphere + cursor_type_radio_circle); @@ -132,15 +128,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_left_width); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_type")); @@ -177,18 +166,9 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) auto clp_dist = float(m_c->object_clipper()->get_position()); ImGui::SameLine(sliders_left_width); -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) m_c->object_clipper()->set_position(clp_dist, true); -#else - ImGui::PushItemWidth(window_width - sliders_left_width); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 51551281a..37a107346 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -144,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<float, 4> 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; @@ -226,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<GLModel*>(&m_cylinder)->set_color(-1, render_color); if (shader) shader->set_uniform("emission_factor", 0.5f); @@ -651,11 +648,7 @@ RENDER_AGAIN: if ((last_h != win_h) || (last_y != y)) { // ask canvas for another frame to render the window in the correct position -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT m_imgui->set_requires_extra_frame(); -#else - m_parent.request_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT if (last_h != win_h) last_h = win_h; if (last_y != y) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index d2a7e0d73..cc53f267c 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<float, 4>& 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 55bcc90a9..3e98374af 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -615,7 +615,7 @@ void NotificationManager::HintNotification::count_lines() float width_of_a = ImGui::CalcTextSize("a").x; int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { - letter_count++; + letter_count += get_utf8_sequence_length(text, last_end + letter_count); } m_endlines.push_back(last_end + letter_count); last_end += letter_count; @@ -685,7 +685,7 @@ void NotificationManager::HintNotification::count_lines() float width_of_a = ImGui::CalcTextSize("a").x; int letter_count = (int)((m_window_width - m_window_width_offset - size_of_last_line) / width_of_a); while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) { - letter_count++; + letter_count += get_utf8_sequence_length(text, last_end + letter_count); } m_endlines2.push_back(last_end + letter_count); last_end += letter_count; @@ -1058,4 +1058,4 @@ void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true } } } //namespace Slic3r -} //namespace GUI
\ No newline at end of file +} //namespace GUI diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 62af8012b..01eeef342 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -8,10 +8,8 @@ #include <boost/format.hpp> #include <boost/log/trivial.hpp> #include <boost/filesystem.hpp> -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #include <boost/algorithm/string/predicate.hpp> #include <boost/nowide/convert.hpp> -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #include <wx/string.h> #include <wx/event.h> @@ -27,6 +25,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "3DScene.hpp" #include "GUI.hpp" #include "I18N.hpp" @@ -53,10 +52,8 @@ static const std::map<const wchar_t, std::string> font_icons = { {ImGui::RightArrowHoverButton , "notification_right_hover" }, {ImGui::PreferencesButton , "notification_preferences" }, {ImGui::PreferencesHoverButton, "notification_preferences_hover"}, -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT {ImGui::SliderFloatEditBtnIcon, "edit_button" }, {ImGui::SliderFloatEditBtnPressedIcon, "edit_button_pressed" }, -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #if ENABLE_LEGEND_TOOLBAR_ICONS {ImGui::LegendTravel , "legend_travel" }, {ImGui::LegendWipe , "legend_wipe" }, @@ -71,6 +68,7 @@ static const std::map<const wchar_t, std::string> font_icons = { {ImGui::LegendToolMarker , "legend_toolmarker" }, #endif // ENABLE_LEGEND_TOOLBAR_ICONS }; + static const std::map<const wchar_t, std::string> font_icons_large = { {ImGui::CloseNotifButton , "notification_close" }, {ImGui::CloseNotifHoverButton , "notification_close_hover" }, @@ -96,14 +94,14 @@ static const std::map<const wchar_t, std::string> 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() { @@ -528,7 +526,6 @@ void ImGuiWrapper::tooltip(const wxString &label, float wrap_width) ImGui::EndTooltip(); } -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImVec2 ImGuiWrapper::get_slider_icon_size() const { return this->calc_button_size(std::wstring(&ImGui::SliderFloatEditBtnIcon, 1)); @@ -677,26 +674,6 @@ bool ImGuiWrapper::image_button(ImTextureID user_texture_id, const ImVec2& size, 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); } -#else -bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/) -{ - bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power); - if (clamp) - *v = std::clamp(*v, v_min, v_max); - return ret; -} - -bool ImGuiWrapper::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*/) -{ - return this->slider_float(label.c_str(), v, v_min, v_max, format, power, clamp); -} - -bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/) -{ - auto label_utf8 = into_u8(label); - return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power, clamp); -} -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection, ImGuiComboFlags flags) { @@ -1166,6 +1143,26 @@ ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id) } #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[] = { diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index d85d6b387..f461bc970 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -9,10 +9,13 @@ #include <wx/string.h> #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; @@ -36,9 +39,7 @@ class ImGuiWrapper unsigned m_mouse_buttons{ 0 }; bool m_disabled{ false }; bool m_new_frame_open{ false }; -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT bool m_requires_extra_frame{ false }; -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #if ENABLE_LEGEND_TOOLBAR_ICONS std::map<wchar_t, int> m_custom_glyph_rects_ids; #endif // ENABLE_LEGEND_TOOLBAR_ICONS @@ -111,18 +112,12 @@ public: void tooltip(const wxString &label, float wrap_width); // Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true). -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT ImVec2 get_slider_icon_size() const; bool slider_float(const char* 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 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 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); -#else - bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true); - 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); - 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); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT // Use selection = -1 to not mark any option as selected bool combo(const wxString& label, const std::vector<std::string>& options, int& selection, ImGuiComboFlags flags = 0); @@ -139,11 +134,14 @@ public: bool want_text_input() const; bool want_any_input() const; -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT bool requires_extra_frame() const { return m_requires_extra_frame; } void set_requires_extra_frame() { m_requires_extra_frame = true; } void reset_requires_extra_frame() { m_requires_extra_frame = false; } -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT + + 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); diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index 38ea95f2c..81d5e01fe 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -199,7 +199,10 @@ namespace instance_check_internal //else // BOOST_LOG_TRIVIAL(error) << "success delete lockfile " << path; #ifdef __APPLE__ - send_message_mac_closing(GUI::wxGetApp().get_instance_hash_string(),GUI::wxGetApp().get_instance_hash_string()); + // Partial fix of #7583 + // On price of incorrect working of single instances on older OSX + if (wxPlatformInfo::Get().GetOSMajorVersion() > 12) + send_message_mac_closing(GUI::wxGetApp().get_instance_hash_string(),GUI::wxGetApp().get_instance_hash_string()); #endif } } diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 2771f9d27..3c7dad0a6 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<std::string> &strings, const std::string &delim = "\n") { @@ -215,10 +209,21 @@ static std::string concat_strings(const std::set<std::string> &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<arrangement::ArrangePolygon> diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index a5ecc0c83..106cc57dd 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 <optional> + +#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<ModelInstance*> 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; +public: - void on_exception(const std::exception_ptr &) override; + void prepare(); - void process() override; + void process(Ctl &ctl) override; -public: - ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + ArrangeJob(); - int status_range() const override + 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<arrangement::ArrangePolygon> 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 000000000..a4f22be55 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp @@ -0,0 +1,181 @@ +#include <exception> + +#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<StatusInfo>(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<JobEntry>(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<MainThreadCallData >(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<void> BoostThreadWorker::call_on_main_thread(std::function<void ()> fn) +{ + MainThreadCallData cbdata{std::move(fn), {}}; + std::future<void> future = cbdata.promise.get_future(); + + m_output_queue.push(std::move(cbdata)); + + return future; +} + +BoostThreadWorker::BoostThreadWorker(std::shared_ptr<ProgressIndicator> 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> 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 000000000..2fe39c0a7 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp @@ -0,0 +1,140 @@ +#ifndef BOOSTTHREADWORKER_HPP +#define BOOSTTHREADWORKER_HPP + +#include <boost/variant.hpp> + +#include "Worker.hpp" + +#include <libslic3r/Thread.hpp> +#include <boost/log/trivial.hpp> + +#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> 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<void()> fn; + std::promise<void> promise; + }; + + struct EmptyMessage {}; + + class WorkerMessage + { + public: + enum MsgType { Empty, Status, Finalize, MainThreadCall }; + + private: + boost::variant<EmptyMessage, StatusInfo, JobEntry, MainThreadCallData> 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<JobEntry>; + using MessageQueue = ThreadSafeQueueSPSC<WorkerMessage>; + + boost::thread m_thread; + std::atomic<bool> m_running{false}, m_canceled{false}; + std::shared_ptr<ProgressIndicator> 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<void> call_on_main_thread(std::function<void()> fn) override; + +public: + explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri, + boost::thread::attributes & attr, + const char * name = ""); + + explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri, + boost::thread::attributes && attr, + const char * name = "") + : BoostThreadWorker{std::move(pri), attr, name} + {} + + explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> 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> 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 000000000..530213b1d --- /dev/null +++ b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp @@ -0,0 +1,48 @@ +#ifndef BUSYCURSORJOB_HPP +#define BUSYCURSORJOB_HPP + +#include "Job.hpp" + +#include <wx/utils.h> + +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 JobSubclass> +class BusyCursored: public Job { + JobSubclass m_job; + +public: + template<class... Args> + BusyCursored(Args &&...args) : m_job{std::forward<Args>(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 870f31f2f..c7d69eb50 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 bf407656d..b1417bbbd 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<ProgressIndicator> pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + void prepare(); + void process(Ctl &ctl) override; + + FillBedJob(); - int status_range() const override + 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 9d0d4bc80..000000000 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include <algorithm> -#include <exception> - -#include "Job.hpp" -#include <libslic3r/Thread.hpp> -#include <boost/log/trivial.hpp> - -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<ProgressIndicator> 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<bool> 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<GUI::Job> &j) { - return j->is_running(); - }); -} - -} - diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index 8243ce943..824c0b830 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -3,119 +3,53 @@ #include <atomic> #include <exception> +#include <future> #include "libslic3r/libslic3r.h" - -#include <slic3r/GUI/I18N.hpp> - #include "ProgressIndicator.hpp" -#include <wx/event.h> +namespace Slic3r { namespace GUI { -#include <boost/thread.hpp> +// 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: -namespace Slic3r { namespace GUI { + // 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; -// 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<bool> m_running{false}, m_canceled{false}; - bool m_finalized = false, m_finalizing = false; - std::shared_ptr<ProgressIndicator> 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 = ""); + // status update, to be used from the work thread (process() method) + virtual void update_status(int st, const std::string &msg = "") = 0; - bool was_canceled() const { return m_canceled.load(); } + // Returns true if the job was asked to cancel itself. + virtual bool was_canceled() const = 0; - // Launched just before start(), a job can use it to prepare internals - virtual void prepare() {} + // 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<void> object which enables the + // caller to optionally wait for the main thread to finish the function call. + virtual std::future<void> call_on_main_thread(std::function<void()> fn) = 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; } + virtual ~Job() = default; - // 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<ProgressIndicator> 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); } -}; + // 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; -// 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<std::unique_ptr<GUI::Job>> m_jobs; - -protected: - virtual void before_start() {} - -public: - virtual ~ExclusiveJobGroup() = default; - - size_t add_job(std::unique_ptr<GUI::Job> &&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; + // 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 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 cb7170568..f398f7333 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 6b03af69d..b31cb7f7c 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 4af205d41..000000000 --- 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 fcf0a54b8..000000000 --- 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<ProgressIndicator> 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 000000000..573590272 --- /dev/null +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -0,0 +1,127 @@ +#ifndef PLATERWORKER_HPP +#define PLATERWORKER_HPP + +#include <map> + +#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 WorkerSubclass> +class PlaterWorker: public Worker { + WorkerSubclass m_w; + Plater *m_plater; + + class PlaterJob : public Job { + std::unique_ptr<Job> 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<void> call_on_main_thread(std::function<void()> 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<Job> 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<class... WorkerArgs> + PlaterWorker(Plater *plater, WorkerArgs &&...args) + : m_w{std::forward<WorkerArgs>(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> job) override + { + return m_w.push(std::make_unique<PlaterJob>(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 95821a674..e88d24fcd 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 <slic3r/GUI/I18N.hpp> + 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 cdb367f23..71a28deb7 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<Vec2d(const ModelObject & mo, const sla::RotOptimizeParams ¶ms)>; @@ -44,19 +45,16 @@ class RotoptimizeJob : public PlaterJob }; std::vector<ObjRot> m_selected_object_ids; - -protected: - - void prepare() override; - void process() override; + Plater *m_plater; public: - RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + void prepare(); + void process(Ctl &ctl) override; + + RotoptimizeJob(); - void finalize() override; + 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 000000000..7dbecff2a --- /dev/null +++ b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp @@ -0,0 +1,114 @@ +#ifndef SLAIMPORTDIALOG_HPP +#define SLAIMPORTDIALOG_HPP + +#include "SLAImportJob.hpp" + +#include <wx/dialog.h> +#include <wx/stattext.h> +#include <wx/combobox.h> +#include <wx/filename.h> +#include <wx/filepicker.h> + +#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<wxString> 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<wxString> 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 0d42cec2d..1bb8cdf6c 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 <wx/dialog.h> -#include <wx/stattext.h> -#include <wx/combobox.h> #include <wx/filename.h> -#include <wx/filepicker.h> 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<wxString> 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<wxString> 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<ProgressIndicator> pri, Plater *plater) - : PlaterJob{std::move(pri), plater}, p{std::make_unique<priv>(plater)} -{} +SLAImportJob::SLAImportJob(const SLAImportJobView *view) + : p{std::make_unique<priv>(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 c2ca10ef6..b2aea8bf8 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<priv> p; - -protected: - void prepare() override; - void process() override; - void finalize() override; + using Sel = SLAImportJobView::Sel; public: - SLAImportJob(std::shared_ptr<ProgressIndicator> 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 000000000..d40049013 --- /dev/null +++ b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp @@ -0,0 +1,123 @@ +#ifndef THREADSAFEQUEUE_HPP +#define THREADSAFEQUEUE_HPP + +#include <type_traits> +#include <queue> +#include <mutex> +#include <condition_variable> +#include <atomic> + +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<bool> *pop_flag = nullptr; +}; + +// A thread safe queue for one producer and one consumer. +template<class T, + template<class, class...> class Container = std::deque, + class... ContainerArgs> +class ThreadSafeQueueSPSC +{ + std::queue<T, Container<T, ContainerArgs...>> m_queue; + mutable std::mutex m_mutex; + std::condition_variable m_cond_var; +public: + + // Consume one element, block if the queue is empty. + template<class Fn> bool consume_one(const BlockingWait &blkw, Fn &&fn) + { + static_assert(!std::is_reference_v<T>, ""); + static_assert(std::is_default_constructible_v<T>, ""); + static_assert(std::is_move_assignable_v<T> || std::is_copy_assignable_v<T>, ""); + + 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<T>) + 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<class Fn> bool consume_one(Fn &&fn) + { + T el; + { + std::unique_lock lk{m_mutex}; + if (!m_queue.empty()) { + if constexpr (std::is_move_assignable_v<T>) + 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<class...TArgs> void push(TArgs&&...el) + { + std::lock_guard lk{m_mutex}; + m_queue.emplace(std::forward<TArgs>(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 000000000..0bc7bc086 --- /dev/null +++ b/src/slic3r/GUI/Jobs/Worker.hpp @@ -0,0 +1,119 @@ +#ifndef PRUSALSICER_WORKER_HPP +#define PRUSALSICER_WORKER_HPP + +#include <memory> + +#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> 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<class Fn> constexpr bool IsProcessFn = std::is_invocable_v<Fn, Job::Ctl&>; +template<class Fn> constexpr bool IsFinishFn = std::is_invocable_v<Fn, bool, std::exception_ptr&>; + +// Helper function to use the worker with arbitrary functors. +template<class ProcessFn, class FinishFn, + class = std::enable_if_t<IsProcessFn<ProcessFn>>, + class = std::enable_if_t<IsFinishFn<FinishFn>> > +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<LambdaJob>(std::move(fn), std::move(finishfn)); + return w.push(std::move(j)); +} + +template<class ProcessFn, class = std::enable_if_t<IsProcessFn<ProcessFn>>> +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<Job> 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<class...Args> bool replace_job(Worker &w, Args&& ...args) +{ + w.cancel_all(); + return queue_job(w, std::forward<Args>(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/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 273417eee..dd6f45318 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -574,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, @@ -1073,7 +1073,7 @@ static wxMenu* generate_help_menu() append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"), [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"), - [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); }); + [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases", nullptr, false); }); //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# wxTheApp->check_version(1); //# }); @@ -1090,7 +1090,7 @@ static wxMenu* generate_help_menu() append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"), [](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME), - [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/slic3r/issues/new"); }); + [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/slic3r/issues/new", nullptr, false); }); if (wxGetApp().is_editor()) append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), [](wxCommandEvent&) { Slic3r::GUI::about(); }); @@ -1211,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"), @@ -1431,7 +1431,7 @@ void MainFrame::init_menubar_as_editor() [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"), + 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 diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 80a8159c0..a2d65a73c 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -14,7 +14,9 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" +#include "format.hpp" #include "I18N.hpp" #include "ConfigWizard.hpp" #include "wxExtensions.hpp" @@ -24,7 +26,6 @@ namespace Slic3r { namespace GUI { - MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, long style, wxBitmap bitmap) : wxDialog(parent ? parent : dynamic_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , boldfont(wxGetApp().normal_font()) @@ -136,14 +137,10 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); wxFont monospace = wxGetApp().code_font(); -#ifdef _WIN32 wxColour text_clr = wxGetApp().get_label_clr_default(); -#else - wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); -#endif - 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); @@ -186,13 +183,26 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin } html->SetMinSize(page_size); - std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg); + std::string msg_escaped = xml_escape(into_u8(msg), is_marked_msg); boost::replace_all(msg_escaped, "\r\n", "<br>"); boost::replace_all(msg_escaped, "\n", "<br>"); if (monospaced_font) // Code formatting will be preserved. This is useful for reporting errors from the placeholder parser. msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>"; - html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>"); + html->SetPage(format_wxstr("<html>" + "<body bgcolor=%1% link=%2%>" + "<font color=%2%>" + "%3%" + "</font>" + "</body>" + "</html>", + bgr_clr_str, text_clr_str, from_u8(msg_escaped))); + + html->Bind(wxEVT_HTML_LINK_CLICKED, [parent](wxHtmlLinkEvent& event) { + wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref(), parent, false); + event.Skip(false); + }); + content_sizer->Add(html, 1, wxEXPAND); wxGetApp().UpdateDarkUI(html); } diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 66c22cb9b..aab0dbbab 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -301,7 +301,7 @@ void NotificationManager::PopNotification::count_lines() float width_of_a = ImGui::CalcTextSize("a").x; int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { - letter_count++; + letter_count += get_utf8_sequence_length(text, last_end + letter_count); } m_endlines.push_back(last_end + letter_count); last_end += letter_count; diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index ee19e5da5..83537fd54 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -699,12 +699,7 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_text(wxDC& dc, wxPoint pos, const wxStr #else dc.SetFont(old_font.Bold().Underlined()); #endif - dc.SetTextForeground(color ? *color : -#ifdef _WIN32 - wxGetApp().get_label_clr_default()); -#else - wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); -#endif /* _WIN32 */ + dc.SetTextForeground(color ? *color : wxGetApp().get_label_clr_default()); dc.DrawText(out_text, pos); dc.SetTextForeground(old_clr); dc.SetFont(old_font); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 64fc4b8c8..3238b0650 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -977,36 +977,7 @@ wxString OptionsGroup::get_url(const std::string& path_end) bool OptionsGroup::launch_browser(const std::string& path_end) { - bool launch = true; - - if (get_app_config()->get("suppress_hyperlinks").empty()) { - wxWindow* parent = wxGetApp().mainframe->m_tabpanel; - RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxYES_NO); - dialog.ShowCheckBox(_L("Remember my choice")); - int answer = dialog.ShowModal(); - if (answer == wxID_CANCEL) - return false; - - if (dialog.IsCheckBoxChecked()) { - wxString preferences_item = _L("Suppress to open hyperlink in browser"); - wxString msg = - _L("PrusaSlicer will remember your choice.") + "\n\n" + - _L("You will not be asked about it again on label hovering.") + "\n\n" + - format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); - - MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); - if (msg_dlg.ShowModal() == wxID_CANCEL) - return false; - - get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : ""); - } - - launch = answer == wxID_YES; - } - if (launch) - launch = get_app_config()->get("suppress_hyperlinks") != "1"; - - return launch && wxLaunchDefaultBrowser(OptionsGroup::get_url(path_end)); + return wxGetApp().open_browser_with_warning_dialog(OptionsGroup::get_url(path_end), wxGetApp().mainframe->m_tabpanel); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1b3c001a4..21aa5fc12 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" @@ -251,9 +254,9 @@ void ObjectInfo::update_warning_icon(const std::string& warning_icon_name) enum SlicedInfoIdx { + siFilament_g, siFilament_m, siFilament_mm3, - siFilament_g, siMateril_unit, siCost, siEstimatedTime, @@ -293,9 +296,9 @@ SlicedInfo::SlicedInfo(wxWindow *parent) : info_vec.push_back(std::pair<wxStaticText*, wxStaticText*>(text, info_label)); }; + init_info_label(_L("Used Filament (g)")); init_info_label(_L("Used Filament (m)")); init_info_label(_L("Used Filament (mm³)")); - init_info_label(_L("Used Filament (g)")); init_info_label(_L("Used Material (unit)")); init_info_label(_L("Cost (money)")); init_info_label(_L("Estimated printing time")); @@ -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<NotificationProgressIndicator> m_pri; - - void before_start() override { m->background_process.stop(); } - - public: - Jobs(priv *_m) : - m(_m), - m_pri{std::make_shared<NotificationProgressIndicator>(m->notification_manager.get())} - { - m_arrange_id = add_job(std::make_unique<ArrangeJob>(m_pri, m->q)); - m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m_pri, m->q)); - m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m_pri, m->q)); - m_sla_import_id = add_job(std::make_unique<SLAImportJob>(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<BoostThreadWorker> m_worker; + SLAImportDialog * m_sla_import_dlg; bool delayed_scene_refresh; std::string delayed_error_message; @@ -1720,8 +1681,33 @@ struct Plater::priv fs::path output_file = get_export_file_path(FT_3MF); suggested_project_name = output_file.empty() ? _L("Untitled") : from_u8(output_file.stem().string()); } - res = MessageDialog(mainframe, reason + "\n" + format_wxstr(_L("Do you want to save the changes to \"%1%\"?"), suggested_project_name), - wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL).ShowModal(); + + std::string act_key = "default_action_on_dirty_project"; + std::string act = wxGetApp().app_config->get(act_key); + if (act.empty()) { + RichMessageDialog dialog(mainframe, reason + "\n" + format_wxstr(_L("Do you want to save the changes to \"%1%\"?"), suggested_project_name), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL); + dialog.ShowCheckBox(_L("Remember my choice")); + res = dialog.ShowModal(); + if (res != wxID_CANCEL) + if (dialog.IsCheckBoxChecked()) { + wxString preferences_item = _L("Ask for unsaved changes in project"); + wxString msg = + _L("PrusaSlicer will remember your choice.") + "\n\n" + + _L("You will not be asked about it again, when: \n" + "- Closing PrusaSlicer,\n" + "- Loading or creating a new project") + "\n\n" + + format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); + + MessageDialog msg_dlg(mainframe, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); + if (msg_dlg.ShowModal() == wxID_CANCEL) + return wxID_CANCEL; + + get_app_config()->set(act_key, res == wxID_YES ? "1" : "0"); + } + } + else + res = (act == "1") ? wxID_YES : wxID_NO; + if (res == wxID_YES) if (!mainframe->save_project_as(project_name)) res = wxID_CANCEL; @@ -1923,9 +1909,7 @@ struct Plater::priv bool can_reload_from_disk() const; bool can_replace_with_stl() const; bool can_split(bool to_objects) const; -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT bool can_scale_to_print_volume() const; -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type); ThumbnailsList generate_thumbnails(const ThumbnailsParams& params, Camera::EType camera_type); @@ -1997,7 +1981,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , notification_manager(std::make_unique<NotificationManager>(q)) - , m_ui_jobs(this) + , m_worker{q, std::make_unique<NotificationProgressIndicator>(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") @@ -2933,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. @@ -2948,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(); @@ -2966,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(); @@ -2998,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(); @@ -3099,11 +3084,7 @@ void Plater::priv::split_volume() void Plater::priv::scale_selection_to_fit_print_volume() { -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT this->view3D->get_canvas3d()->get_selection().scale_to_fit_print_volume(this->bed.build_volume()); -#else - this->view3D->get_canvas3d()->get_selection().scale_to_fit_print_volume(*config); -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT } void Plater::priv::schedule_background_process() @@ -3299,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; } @@ -3385,11 +3366,39 @@ void Plater::priv::update_sla_scene() this->update_restart_background_process(true, true); } +// class used to show a wxBusyCursor and a wxBusyInfo +// and hide them on demand +class Busy +{ + wxWindow* m_parent{ nullptr }; + std::unique_ptr<wxBusyCursor> m_cursor; + std::unique_ptr<wxBusyInfo> m_dlg; + +public: + Busy(const wxString& message, wxWindow* parent = nullptr) { + m_parent = parent; + m_cursor = std::make_unique<wxBusyCursor>(); + m_dlg = std::make_unique<wxBusyInfo>(message, m_parent); + } + + ~Busy() { reset(); } + + void update(const wxString& message) { + // this is ugly but necessary because the call to wxBusyInfo::UpdateLabel() is not working [WX 3.1.4] + m_dlg = std::make_unique<wxBusyInfo>(message, m_parent); +// m_dlg->UpdateLabel(message); + } + + void reset() { + m_cursor.reset(); + m_dlg.reset(); + } +}; + bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const fs::path& new_path, const wxString& snapshot) { const std::string path = new_path.string(); - wxBusyCursor wait; - wxBusyInfo info(_L("Replace from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); + Busy busy(_L("Replace from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); Model new_model; try { @@ -3399,8 +3408,10 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const model_object->ensure_on_bed(); } } - catch (std::exception&) { + catch (std::exception& e) { // error while loading + busy.reset(); + GUI::show_error(q, e.what()); return false; } @@ -3624,12 +3635,13 @@ void Plater::priv::reload_from_disk() std::vector<wxString> fail_list; + Busy busy(_L("Reload from:"), q->get_current_canvas3D()->get_wxglcanvas()); + // load one file at a time for (size_t i = 0; i < input_paths.size(); ++i) { const auto& path = input_paths[i].string(); - wxBusyCursor wait; - wxBusyInfo info(_L("Reload from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); + busy.update(_L("Reload from:") + " " + from_u8(path)); Model new_model; try @@ -3640,9 +3652,11 @@ void Plater::priv::reload_from_disk() model_object->ensure_on_bed(); } } - catch (std::exception&) + catch (std::exception& e) { // error while loading + busy.reset(); + GUI::show_error(q, e.what()); return; } @@ -3717,15 +3731,15 @@ void Plater::priv::reload_from_disk() } } + busy.reset(); + for (size_t i = 0; i < replace_paths.size(); ++i) { const auto& path = replace_paths[i].string(); for (const SelectedVolume& sel_v : selected_volumes) { - ModelObject* old_model_object = model.objects[sel_v.object_idx]; - ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; - bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); - if (!replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, "")) { - fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); - } +// ModelObject* old_model_object = model.objects[sel_v.object_idx]; +// ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; +// bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); + replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, ""); } } @@ -3927,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; } @@ -4487,13 +4501,11 @@ bool Plater::priv::can_split(bool to_objects) const return sidebar->obj_list()->is_splittable(to_objects); } -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT bool Plater::priv::can_scale_to_print_volume() const { const BuildVolume::Type type = this->bed.build_volume().type(); return !view3D->get_canvas3d()->get_selection().is_empty() && (type == BuildVolume::Type::Rectangle || type == BuildVolume::Type::Circle); } -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT bool Plater::priv::layers_height_allowed() const { @@ -4618,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; @@ -4628,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; @@ -4648,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 @@ -5102,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<SLAImportJob>(p->m_sla_import_dlg)); + } } void Plater::extract_config_from_project() @@ -5385,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(); } @@ -5436,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(); } @@ -5545,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<FillBedJob>()); + } } bool Plater::is_selection_empty() const @@ -5738,7 +5753,7 @@ void Plater::export_stl(bool extended, bool selection_only) mesh.merge(m); } } - else if (0 <= instance_id && instance_id < mo.instances.size()) + else if (0 <= instance_id && instance_id < int(mo.instances.size())) mesh.transform(mo.instances[instance_id]->get_matrix(), true); return mesh; @@ -5749,13 +5764,15 @@ 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 = selection.is_single_full_object() ? mesh_to_export(*model_object, -1) : mesh_to_export(*model_object, selection.get_instance_idx()); + 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(); mesh.transform(volume->get_volume_transformation().get_matrix(), true); - mesh.translate(-model_object->origin_translation.cast<float>()); } + + if (!selection.is_single_full_object() || model_object->instances.size() == 1) + mesh.translate(-model_object->origin_translation.cast<float>()); } else { for (const ModelObject* o : p->model.objects) { @@ -5947,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) @@ -6412,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<ArrangeJob>()); + } } void Plater::set_current_canvas_as_dirty() @@ -6584,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); } @@ -6822,9 +6847,7 @@ bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } bool Plater::can_replace_with_stl() const { return p->can_replace_with_stl(); } bool Plater::can_mirror() const { return p->can_mirror(); } bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); } -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT bool Plater::can_scale_to_print_volume() const { return p->can_scale_to_print_volume(); } -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 20a64c6d5..0b26f09e1 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); @@ -307,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; @@ -328,9 +361,7 @@ public: bool can_replace_with_stl() const; bool can_mirror() const; bool can_split(bool to_objects) const; -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT bool can_scale_to_print_volume() const; -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT void msw_rescale(); void sys_color_changed(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index fa1e84ea6..4b218bf66 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -175,6 +175,8 @@ void PreferencesDialog::build() 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<bool>(value) ? "none" : "discard"; + else if (opt_key == "default_action_on_dirty_project") + m_values[opt_key] = boost::any_cast<bool>(value) ? "" : "0"; else m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0"; }; @@ -259,21 +261,30 @@ void PreferencesDialog::build() m_optgroup_general->append_separator(); + 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"), + app_config->get("default_action_on_dirty_project").empty()); + + m_optgroup_general->append_separator(); + append_bool_option(m_optgroup_general, "default_action_on_close_application", - L("Ask to save unsaved changes when closing the application or when loading a new project"), - L("Always ask for unsaved changes, when: \n" + 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"), app_config->get("default_action_on_close_application") == "none"); append_bool_option(m_optgroup_general, "default_action_on_select_preset", - L("Ask for unsaved changes when selecting new preset"), - L("Always ask for unsaved changes when selecting new preset or resetting a 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"); append_bool_option(m_optgroup_general, "default_action_on_new_project", - L("Ask for unsaved changes when creating new project"), - L("Always ask for unsaved changes when creating 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 @@ -301,6 +312,11 @@ void PreferencesDialog::build() L("Show splash screen"), app_config->get("show_splash_screen") == "1"); + 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 append_bool_option(m_optgroup_general, "clear_undo_redo_stack_on_new_project", L("Clear Undo / Redo stack on new project"), @@ -377,8 +393,9 @@ void PreferencesDialog::build() append_bool_option(m_optgroup_gui, "suppress_hyperlinks", L("Suppress to open hyperlink in 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."), + 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"); append_bool_option(m_optgroup_gui, "color_mapinulation_panel", @@ -568,10 +585,17 @@ void PreferencesDialog::accept(wxEvent&) } } - for (const std::string& key : {"default_action_on_close_application", "default_action_on_select_preset"}) { + for (const std::string& key : { "default_action_on_close_application", + "default_action_on_select_preset", + "default_action_on_new_project" }) { auto it = m_values.find(key); if (it != m_values.end() && it->second != "none" && app_config->get(key) != "none") - m_values.erase(it); // we shouldn't change value, if some of those parameters was selected, and then deselected + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + } + { + auto it = m_values.find("default_action_on_dirty_project"); + if (it != m_values.end() && !it->second.empty() && !app_config->get("default_action_on_dirty_project").empty()) + m_values.erase(it); // we shouldn't change value, if this parameter was selected, and then deselected } #if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 2b53883be..ed4888a87 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/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 09ba48b58..af2494dc8 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -247,7 +247,7 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); append_text_column(_L("Status"),widths[2]); append_text_column(_L("Host"), widths[3]); - append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]); + append_text_column(_CTX(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]); append_text_column(_L("Filename"), widths[5]); append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN); @@ -324,7 +324,7 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) } else stream << std::fixed << std::setprecision(2) << ((float)size_i / 1024 / 1024) << "MB"; fields.push_back(wxVariant(stream.str())); - fields.push_back(wxVariant(job.upload_data.upload_path.string())); + fields.push_back(wxVariant(from_path(job.upload_data.upload_path))); fields.push_back(wxVariant("")); job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW)); // Both strings are UTF-8 encoded. diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 406617d5a..5aa2cb4d0 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -15,16 +15,16 @@ #include "libslic3r/LocalesUtils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT #include "libslic3r/BuildVolume.hpp" -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT #include <GL/glew.h> #include <boost/algorithm/string/predicate.hpp> #include <boost/log/trivial.hpp> -static const std::array<float, 4> UNIFORM_SCALE_COLOR = { 0.923f, 0.504f, 0.264f, 1.0f }; +static const Slic3r::ColorRGBA UNIFORM_SCALE_COLOR = Slic3r::ColorRGBA::ORANGE(); +static const Slic3r::ColorRGBA SOLID_PLANE_COLOR = Slic3r::ColorRGBA::ORANGE(); +static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5f }; namespace Slic3r { namespace GUI { @@ -448,25 +448,17 @@ void Selection::clear() if (m_list.empty()) return; -#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT // ensure that the volumes get the proper color before next call to render (expecially needed for transparent volumes) for (unsigned int i : m_list) { GLVolume& volume = *(*m_volumes)[i]; volume.selected = false; - bool transparent = volume.color[3] < 1.0f; - if (transparent) + bool is_transparent = volume.color.is_transparent(); + if (is_transparent) volume.force_transparent = true; volume.set_render_color(); - if (transparent) + if (is_transparent) volume.force_transparent = false; } -#else - for (unsigned int i : m_list) { - (*m_volumes)[i]->selected = false; - // ensure the volume gets the proper color before next call to render (expecially needed for transparent volumes) - (*m_volumes)[i]->set_render_color(); - } -#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT m_list.clear(); @@ -951,7 +943,6 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT void Selection::scale_to_fit_print_volume(const BuildVolume& volume) { auto fit = [this](double s, Vec3d offset) { @@ -1039,50 +1030,6 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume) default: { break; } } } -#else -void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) -{ - if (is_empty() || m_mode == Volume) - return; - - // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings - Vec3d box_size = get_bounding_box().size() + 0.01 * Vec3d::Ones(); - - const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config.option("bed_shape")); - if (opt != nullptr) { - BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume({ unscale<double>(bed_box_2D.min(0)), unscale<double>(bed_box_2D.min(1)), 0.0 }, { unscale<double>(bed_box_2D.max(0)), unscale<double>(bed_box_2D.max(1)), config.opt_float("max_print_height") }); - Vec3d print_volume_size = print_volume.size(); - double sx = (box_size(0) != 0.0) ? print_volume_size(0) / box_size(0) : 0.0; - double sy = (box_size(1) != 0.0) ? print_volume_size(1) / box_size(1) : 0.0; - double sz = (box_size(2) != 0.0) ? print_volume_size(2) / box_size(2) : 0.0; - if (sx != 0.0 && sy != 0.0 && sz != 0.0) - { - double s = std::min(sx, std::min(sy, sz)); - if (s != 1.0) { - wxGetApp().plater()->take_snapshot(_L("Scale To Fit")); - - TransformationType type; - type.set_world(); - type.set_relative(); - type.set_joint(); - - // apply scale - start_dragging(); - scale(s * Vec3d::Ones(), type); - wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot - - // center selection on print bed - start_dragging(); - translate(print_volume.center() - get_bounding_box().center()); - wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot - - wxGetApp().obj_manipul()->set_dirty(); - } - } - } -} -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT void Selection::mirror(Axis axis) { @@ -1919,10 +1866,10 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons glsafe(::glEnd()); } -static std::array<float, 4> get_color(Axis axis) +static ColorRGBA get_color(Axis axis) { - return { AXES_COLOR[axis][0], AXES_COLOR[axis][1], AXES_COLOR[axis][2], AXES_COLOR[axis][3] }; -}; + return AXES_COLOR[axis]; +} void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const { @@ -2052,10 +1999,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); ::glBegin(GL_QUADS); - if ((camera_on_top && type == 1) || (!camera_on_top && type == 2)) - ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); - else - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glColor4fv((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? + SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); ::glVertex3f(min_x, min_y, z1); ::glVertex3f(max_x, min_y, z1); ::glVertex3f(max_x, max_y, z1); @@ -2063,10 +2008,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co glsafe(::glEnd()); ::glBegin(GL_QUADS); - if ((camera_on_top && type == 2) || (!camera_on_top && type == 1)) - ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); - else - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glColor4fv((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? + SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); ::glVertex3f(min_x, min_y, z2); ::glVertex3f(max_x, min_y, z2); ::glVertex3f(max_x, max_y, z2); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c18d24eab..537c07207 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -17,9 +17,7 @@ class GLArrow; class GLCurvedArrow; class DynamicPrintConfig; class GLShaderProgram; -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT class BuildVolume; -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT using GLVolumePtrs = std::vector<GLVolume*>; using ModelObjectPtrs = std::vector<ModelObject*>; @@ -323,11 +321,7 @@ public: void rotate(const Vec3d& rotation, TransformationType transformation_type); void flattening_rotate(const Vec3d& normal); void scale(const Vec3d& scale, TransformationType transformation_type); -#if ENABLE_ENHANCED_PRINT_VOLUME_FIT void scale_to_fit_print_volume(const BuildVolume& volume); -#else - void scale_to_fit_print_volume(const DynamicPrintConfig& config); -#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT void mirror(Axis axis); void translate(unsigned int object_idx, const Vec3d& displacement); diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index e8e23a44e..9a97532a7 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -4,6 +4,7 @@ #include "libslic3r/BlacklistedLibraryCheck.hpp" #include "libslic3r/Platform.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/Http.hpp" @@ -571,9 +572,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) wxColour bgr_clr = wxGetApp().get_window_default_clr(); SetBackgroundColour(bgr_clr); 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()); - + 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())); auto *topSizer = new wxBoxSizer(wxVERTICAL); auto *vsizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 1a8542438..06cf2b73d 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -15,6 +15,7 @@ #include "MainFrame.hpp" #include "wxExtensions.hpp" #include "../libslic3r/BlacklistedLibraryCheck.hpp" +#include "../libslic3r/Color.hpp" #include "format.hpp" #ifdef _WIN32 @@ -113,9 +114,9 @@ SysInfoDialog::SysInfoDialog() // main_info_text wxFont font = get_default_font(this); - const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - 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 = wxGetApp().get_label_clr_default(); + 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 fs = font.GetPointSize() - 1; int size[] = { static_cast<int>(fs*1.5), static_cast<int>(fs*1.4), static_cast<int>(fs*1.3), fs, fs, fs, fs }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ba7de0cd6..b3db76e99 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2298,10 +2298,13 @@ void TabPrinter::build_fff() } } if (opt_key == "gcode_flavor") { - bool supports_travel_acceleration = (boost::any_cast<int>(value) == int(gcfMarlinFirmware)); - if (supports_travel_acceleration != m_supports_travel_acceleration) { + const int flavor = boost::any_cast<int>(value); + bool supports_travel_acceleration = (flavor == int(gcfMarlinFirmware) || flavor == int(gcfRepRapFirmware)); + bool supports_min_feedrates = (flavor == int(gcfMarlinFirmware) || flavor == int(gcfMarlinLegacy)); + if (supports_travel_acceleration != m_supports_travel_acceleration || supports_min_feedrates != m_supports_min_feedrates) { m_rebuild_kinematics_page = true; m_supports_travel_acceleration = supports_travel_acceleration; + m_supports_min_feedrates = supports_min_feedrates; } } build_unregular_pages(); @@ -2586,9 +2589,11 @@ PageShp TabPrinter::build_kinematics_page() append_option_line(optgroup, "machine_max_jerk_" + axis); } - optgroup = page->new_optgroup(L("Minimum feedrates")); - append_option_line(optgroup, "machine_min_extruding_rate"); - append_option_line(optgroup, "machine_min_travel_rate"); + if (m_supports_min_feedrates) { + optgroup = page->new_optgroup(L("Minimum feedrates")); + append_option_line(optgroup, "machine_min_extruding_rate"); + append_option_line(optgroup, "machine_min_travel_rate"); + } return page; } @@ -2603,7 +2608,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) { size_t n_before_extruders = 2; // Count of pages before Extruder pages auto flavor = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value; - bool is_marlin_flavor = (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware); + bool show_mach_limits = (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware); /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages * and be cause of application crash, when try to change Preset in moment, @@ -2611,26 +2616,26 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) * */ Freeze(); - // Add/delete Kinematics page according to is_marlin_flavor + // Add/delete Kinematics page according to show_mach_limits size_t existed_page = 0; for (size_t i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already if (m_pages[i]->title().find(L("Machine limits")) != std::string::npos) { - if (!is_marlin_flavor || m_rebuild_kinematics_page) + if (!show_mach_limits || m_rebuild_kinematics_page) m_pages.erase(m_pages.begin() + i); else existed_page = i; break; } - if (existed_page < n_before_extruders && (is_marlin_flavor || from_initial_build)) { + if (existed_page < n_before_extruders && (show_mach_limits || from_initial_build)) { auto page = build_kinematics_page(); - if (from_initial_build && !is_marlin_flavor) + if (from_initial_build && !show_mach_limits) page->clear(); else m_pages.insert(m_pages.begin() + n_before_extruders, page); } - if (is_marlin_flavor) + if (show_mach_limits) n_before_extruders++; size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page @@ -2868,13 +2873,13 @@ void TabPrinter::toggle_options() if (!m_active_page || m_presets->get_edited_preset().printer_technology() == ptSLA) return; + const GCodeFlavor flavor = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value; bool have_multiple_extruders = m_extruders_count > 1; if (m_active_page->title() == "Custom G-code") toggle_option("toolchange_gcode", have_multiple_extruders); if (m_active_page->title() == "General") { toggle_option("single_extruder_multi_material", have_multiple_extruders); - auto flavor = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value; bool is_marlin_flavor = flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware; // Disable silent mode for non-marlin firmwares. toggle_option("silent_mode", is_marlin_flavor); @@ -2944,8 +2949,9 @@ void TabPrinter::toggle_options() } if (m_active_page->title() == "Machine limits" && m_machine_limits_description_line) { - assert(m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value == gcfMarlinLegacy - || m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value == gcfMarlinFirmware); + assert(flavor == gcfMarlinLegacy + || flavor == gcfMarlinFirmware + || flavor == gcfRepRapFirmware); const auto *machine_limits_usage = m_config->option<ConfigOptionEnum<MachineLimitsUsage>>("machine_limits_usage"); bool enabled = machine_limits_usage->value != MachineLimitsUsage::Ignore; bool silent_mode = m_config->opt_bool("silent_mode"); @@ -2977,10 +2983,13 @@ void TabPrinter::update_fff() m_use_silent_mode = m_config->opt_bool("silent_mode"); } - bool supports_travel_acceleration = (m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value == gcfMarlinFirmware); - if (m_supports_travel_acceleration != supports_travel_acceleration) { + const auto flavor = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value; + bool supports_travel_acceleration = (flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware); + bool supports_min_feedrates = (flavor == gcfMarlinFirmware || flavor == gcfMarlinLegacy); + if (m_supports_travel_acceleration != supports_travel_acceleration || m_supports_min_feedrates != supports_min_feedrates) { m_rebuild_kinematics_page = true; m_supports_travel_acceleration = supports_travel_acceleration; + m_supports_min_feedrates = supports_min_feedrates; } toggle_options(); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 45da27bf6..8ae2a5fdc 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -408,6 +408,7 @@ private: bool m_has_single_extruder_MM_page = false; bool m_use_silent_mode = false; bool m_supports_travel_acceleration = false; + bool m_supports_min_feedrates = false; void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key); bool m_rebuild_kinematics_page = false; ogStaticText* m_machine_limits_description_line {nullptr}; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 38440b16a..ee057a7aa 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -11,6 +11,7 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" #include "format.hpp" #include "GUI_App.hpp" #include "Plater.hpp" @@ -57,9 +58,8 @@ static std::string get_icon_name(Preset::Type type, PrinterTechnology pt) { static std::string def_text_color() { - wxColour def_colour = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), def_colour.Red(), def_colour.Green(), def_colour.Blue()); - return clr_str.ToStdString(); + wxColour def_colour = wxGetApp().get_label_clr_default(); + return encode_color(ColorRGB(def_colour.Red(), def_colour.Green(), def_colour.Blue())); } static std::string grey = "#808080"; static std::string orange = "#ed6b21"; @@ -124,8 +124,8 @@ wxBitmap ModelNode::get_bitmap(const wxString& color) const int icon_height = lround(1.6 * em); BitmapCache bmp_cache; - unsigned char rgb[3]; - BitmapCache::parse_color(into_u8(color), rgb); + ColorRGB rgb; + decode_color(into_u8(color), rgb); // there is no need to scale created solid bitmap #ifndef __linux__ return bmp_cache.mksolid(icon_width, icon_height, rgb, true); @@ -891,12 +891,12 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ { if (!evt.IsChecked()) return; - wxString preferences_item = m_app_config_key == "default_action_on_new_project" ? _L("Ask for unsaved changes when creating new project") : - m_app_config_key == "default_action_on_select_preset" ? _L("Ask for unsaved changes when selecting new preset") : - _L("Ask to save unsaved changes when closing the application or when loading a new project") ; - wxString action = m_app_config_key == "default_action_on_new_project" ? _L("You will not be asked about the unsaved changes the next time you create new project") : - m_app_config_key == "default_action_on_select_preset" ? _L("You will not be asked about the unsaved changes the next time you switch a preset") : - _L("You will not be asked about the unsaved changes the next time you: \n" + wxString preferences_item = m_app_config_key == "default_action_on_new_project" ? _L("Ask for unsaved changes in presets when creating new project") : + m_app_config_key == "default_action_on_select_preset" ? _L("Ask for unsaved changes in presets when selecting new preset") : + _L("Ask to save unsaved changes in presets when closing the application or when loading a new project") ; + wxString action = m_app_config_key == "default_action_on_new_project" ? _L("You will not be asked about the unsaved changes in presets the next time you create new project") : + m_app_config_key == "default_action_on_select_preset" ? _L("You will not be asked about the unsaved changes in presets the next time you switch a preset") : + _L("You will not be asked about the unsaved changes in presets the next time you: \n" "- Closing PrusaSlicer while some presets are modified,\n" "- Loading a new project while some presets are modified") ; wxString msg = _L("PrusaSlicer will remember your action.") + "\n\n" + action + "\n\n" + @@ -927,7 +927,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name) { - if (action == Action::Undef && !m_has_long_strings) + if (action == Action::Undef && !m_tree->has_long_strings()) m_info_line->Hide(); else { wxString text; @@ -1441,7 +1441,7 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) m_preset_bundle_left = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle); m_preset_bundle_right = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle); - m_top_info_line = new wxStaticText(this, wxID_ANY, "Select presets to compare"); + m_top_info_line = new wxStaticText(this, wxID_ANY, _L("Select presets to compare")); m_top_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); m_bottom_info_line = new wxStaticText(this, wxID_ANY, ""); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 42f93f660..d9154850e 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -221,6 +221,7 @@ public: void item_value_changed(wxDataViewEvent& event); void set_em_unit(int em) { m_em_unit = em; } bool has_unselected_options(); + bool has_long_strings() { return m_has_long_strings; } std::vector<std::string> options(Preset::Type type, bool selected); std::vector<std::string> selected_options(); @@ -240,7 +241,6 @@ class UnsavedChangesDialog : public DPIDialog wxStaticText* m_info_line { nullptr }; wxCheckBox* m_remember_choice { nullptr }; - bool m_has_long_strings { false }; int m_save_btn_id { wxID_ANY }; int m_move_btn_id { wxID_ANY }; int m_continue_btn_id { wxID_ANY }; diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 71b87322f..4d117ff8d 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -7,6 +7,8 @@ #include "GUI_App.hpp" #include "MsgDialog.hpp" +#include "libslic3r/Color.hpp" + #include <wx/sizer.h> int scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit(); } @@ -226,9 +228,9 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, con m_number_of_extruders = (int)(sqrt(matrix.size())+0.001); for (const std::string& color : extruder_colours) { - unsigned char rgb[3]; - Slic3r::GUI::BitmapCache::parse_color(color, rgb); - m_colours.push_back(wxColor(rgb[0], rgb[1], rgb[2])); + Slic3r::ColorRGB rgb; + Slic3r::decode_color(color, rgb); + m_colours.push_back(wxColor(rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar())); } // Create two switched panels with their own sizers diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 9b70657e1..ebd854cc3 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -18,6 +18,8 @@ #include "BitmapComboBox.hpp" #include "OG_CustomCtrl.hpp" +#include "libslic3r/Color.hpp" + #ifndef __linux__ // msw_menuitem_bitmaps is used for MSW and OSX static std::map<int, std::string> msw_menuitem_bitmaps; @@ -472,8 +474,6 @@ std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon/* = false*/) if (colors.empty()) return bmps; - unsigned char rgb[3]; - /* It's supposed that standard size of an icon is 36px*16px for 100% scaled display. * So set sizes for solid_colored icons used for filament preset * and scale them in respect to em_unit value @@ -491,7 +491,8 @@ std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon/* = false*/) wxBitmap* bitmap = bmp_cache.find(bitmap_key); if (bitmap == nullptr) { // Paint the color icon. - Slic3r::GUI::BitmapCache::parse_color(color, rgb); + Slic3r::ColorRGB rgb; + Slic3r::decode_color(color, rgb); // there is no neede to scale created solid bitmap bitmap = bmp_cache.insert(bitmap_key, bmp_cache.mksolid(icon_width, icon_height, rgb, true, 1, dark_mode)); } diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 6f6b21f68..296c58622 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -284,29 +284,32 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path // Open the destination file. FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb"); - - Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; - byte *buffer_ptr; - bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); - { - Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; - buffer.As(&bufferByteAccess); - hr = bufferByteAccess->Buffer(&buffer_ptr); - } - uint32_t length; - hr = buffer->get_Length(&length); - - Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead; - for (;;) { - hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); - status = winrt_async_await(asyncRead, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); + try { + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + byte *buffer_ptr; + bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); + { + Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; + buffer.As(&bufferByteAccess); + hr = bufferByteAccess->Buffer(&buffer_ptr); + } + uint32_t length; hr = buffer->get_Length(&length); - if (length == 0) - break; - fwrite(buffer_ptr, length, 1, fout); - } + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead; + for (;;) { + hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); + status = winrt_async_await(asyncRead, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); + hr = buffer->get_Length(&length); + if (length == 0) + break; + fwrite(buffer_ptr, length, 1, fout); + } + } catch (...) { + fclose(fout); + throw; + } fclose(fout); // Here all the COM objects will be released through the ComPtr destructors. } |