diff --git a/CMakeLists.txt b/CMakeLists.txt index 8557ab0d7d..72fd87d224 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1) option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1) option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0) option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) +option(SLIC3R_UBSAN "Enable UBSan on Clang and GCC" 0) # If SLIC3R_FHS is 1 -> SLIC3R_DESKTOP_INTEGRATION is always 0, othrewise variable. CMAKE_DEPENDENT_OPTION(SLIC3R_DESKTOP_INTEGRATION "Allow perfoming desktop integration during runtime" 1 "NOT SLIC3R_FHS" 0) @@ -239,6 +240,11 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP add_compile_options(-Wno-deprecated-declarations) endif() + # Clang reports misleading indentation for some IF blocks because of mixing tabs with spaces. + if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + add_compile_options(-Wno-misleading-indentation) + endif() + #GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66943 or # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 @@ -266,6 +272,32 @@ if (SLIC3R_ASAN) endif () endif () +if (SLIC3R_UBSAN) + # Stacktrace for every report is enabled by default. It can be disabled by running PrusaSlicer with "UBSAN_OPTIONS=print_stacktrace=0". + + # Define macro SLIC3R_UBSAN to allow detection in the source code if this sanitizer is enabled. + add_compile_definitions(SLIC3R_UBSAN) + + # Clang supports much more useful checks than GCC, so when Clang is detected, another checks will be enabled. + # List of what GCC is checking: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html + # List of what Clang is checking: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(_ubsan_flags "-fsanitize=undefined,integer") + else () + set(_ubsan_flags "-fsanitize=undefined") + endif () + + add_compile_options(${_ubsan_flags} -fno-omit-frame-pointer) + + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${_ubsan_flags}") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${_ubsan_flags}") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${_ubsan_flags}") + + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lubsan") + endif () +endif () + if (APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=partial-availability -Werror=unguarded-availability -Werror=unguarded-availability-new") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=partial-availability -Werror=unguarded-availability -Werror=unguarded-availability-new") diff --git a/deps/Boost/Boost.cmake b/deps/Boost/Boost.cmake index ae2bd96466..15792d5a75 100644 --- a/deps/Boost/Boost.cmake +++ b/deps/Boost/Boost.cmake @@ -28,6 +28,9 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") elseif (MSVC_VERSION LESS 1930) # 1920-1929 = VS 16.0 (v142 toolset) set(_boost_toolset "msvc-14.2") + elseif (MSVC_VERSION LESS 1940) + # 1930-1939 = VS 17.0 (v143 toolset) + set(_boost_toolset "msvc-14.3") else () message(FATAL_ERROR "Unsupported MSVC version") endif () @@ -117,6 +120,12 @@ set(_build_cmd ${_build_cmd} set(_install_cmd ${_build_cmd} --prefix=${_prefix} install) +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # When Clang is used with enabled UndefinedBehaviorSanitizer, it produces "undefined reference to '__muloti4'" when __int128 is used. + # Because of that, UndefinedBehaviorSanitizer is disabled for those functions that use __int128. + list(APPEND _patch_command COMMAND ${PATCH_CMD} ${CMAKE_CURRENT_LIST_DIR}/Boost.patch) +endif () + ExternalProject_Add( dep_Boost URL "https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz" diff --git a/deps/Boost/Boost.patch b/deps/Boost/Boost.patch new file mode 100644 index 0000000000..8c54430b96 --- /dev/null +++ b/deps/Boost/Boost.patch @@ -0,0 +1,23 @@ +diff -u ../boost_1_75_0-orig/boost/rational.hpp ./boost/rational.hpp +--- ../boost_1_75_0-orig/boost/rational.hpp 2020-12-03 06:02:19.000000000 +0100 ++++ ./boost/rational.hpp 2022-01-27 16:02:27.993848905 +0100 +@@ -302,6 +302,9 @@ + return *this; + } + template ++ #if defined(__clang__) ++ __attribute__((no_sanitize("undefined"))) ++ #endif + BOOST_CXX14_CONSTEXPR typename boost::enable_if_c::value, rational&>::type operator*= (const T& i) + { + // Avoid overflow and preserve normalization +@@ -311,6 +314,9 @@ + return *this; + } + template ++ #if defined(__clang__) ++ __attribute__((no_sanitize("undefined"))) ++ #endif + BOOST_CXX14_CONSTEXPR typename boost::enable_if_c::value, rational&>::type operator/= (const T& i) + { + // Avoid repeated construction diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index 7288cfe6fa..aba6fcab49 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -15,6 +15,10 @@ elseif (MSVC_VERSION LESS 1930) # 1920-1929 = VS 16.0 (v142 toolset) set(DEP_VS_VER "16") set(DEP_BOOST_TOOLSET "msvc-14.2") +elseif (MSVC_VERSION LESS 1940) +# 1930-1939 = VS 17.0 (v143 toolset) + set(DEP_VS_VER "17") + set(DEP_BOOST_TOOLSET "msvc-14.3") else () message(FATAL_ERROR "Unsupported MSVC version") endif () diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index c8051e21f9..2730854ebc 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -12,8 +12,8 @@ endif() prusaslicer_add_cmake_project(wxWidgets # GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" # GIT_TAG tm_cross_compile #${_wx_git_tag} - URL https://github.com/prusa3d/wxWidgets/archive/refs/heads/v3.1.4-patched.zip - URL_HASH SHA256=69dec874981d2fc3d90345660c27f3450d8430c483e8446edcc87b6ed18bff8f + URL https://github.com/prusa3d/wxWidgets/archive/73f029adfcc82fb3aa4b01220a013f716e57d110.zip + URL_HASH SHA256=c35fe0187db497b6a3f477e24ed5e307028657ff0c2554385810b6e7961ad2e4 DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG CMAKE_ARGS -DwxBUILD_PRECOMP=ON diff --git a/resources/data/hints.ini b/resources/data/hints.ini index 3558e86ae4..d7c628c07a 100644 --- a/resources/data/hints.ini +++ b/resources/data/hints.ini @@ -34,7 +34,7 @@ # # Open preferences (might add item to highlight) # hypertext_type = preferences -# hypertext_preferences_page = 2 (values 0-2 according to prefernces tab to be opened) +# hypertext_preferences_page = name of the prefernces tab # hypertext_preferences_item = show_collapse_button (name of variable saved in prusaslicer.ini connected to the setting in preferences) # # Open gallery (no aditional var) @@ -97,7 +97,7 @@ documentation_link = https://help.prusa3d.com/en/article/reload-from-disk_120427 [hint:Hiding sidebar] text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut Shift+Tab? You can also enable the icon for this from thePreferences. hypertext_type = preferences -hypertext_preferences_page = 2 +hypertext_preferences_page = GUI hypertext_preferences_item = show_collapse_button [hint:Perspective camera] @@ -214,7 +214,7 @@ disabled_tags = SLA [hint:Settings in non-modal window] text = Settings in non-modal window\nDid you know that you can open the Settings in a new non-modal window? This means you can have settings open on one screen and the G-code Preview on the other. Go to thePreferencesand select Settings in non-modal window. hypertext_type = preferences -hypertext_preferences_page = 2 +hypertext_preferences_page = GUI hypertext_preferences_item = dlg_settings_layout_mode [hint:Adaptive infills] diff --git a/resources/icons/edit_button_pressed.svg b/resources/icons/edit_button_pressed.svg new file mode 100644 index 0000000000..6e4058d10f --- /dev/null +++ b/resources/icons/edit_button_pressed.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_colorchanges.svg b/resources/icons/legend_colorchanges.svg new file mode 100644 index 0000000000..cb95ef4676 --- /dev/null +++ b/resources/icons/legend_colorchanges.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_customgcodes.svg b/resources/icons/legend_customgcodes.svg new file mode 100644 index 0000000000..96e0be69e3 --- /dev/null +++ b/resources/icons/legend_customgcodes.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/resources/icons/legend_deretract.svg b/resources/icons/legend_deretract.svg new file mode 100644 index 0000000000..4b636df9de --- /dev/null +++ b/resources/icons/legend_deretract.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_pauseprints.svg b/resources/icons/legend_pauseprints.svg new file mode 100644 index 0000000000..954bc00e97 --- /dev/null +++ b/resources/icons/legend_pauseprints.svg @@ -0,0 +1,76 @@ + + diff --git a/resources/icons/legend_retract.svg b/resources/icons/legend_retract.svg new file mode 100644 index 0000000000..494e2f7286 --- /dev/null +++ b/resources/icons/legend_retract.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_seams.svg b/resources/icons/legend_seams.svg new file mode 100644 index 0000000000..724414119d --- /dev/null +++ b/resources/icons/legend_seams.svg @@ -0,0 +1,45 @@ + + + + + + + diff --git a/resources/icons/legend_shells.svg b/resources/icons/legend_shells.svg new file mode 100644 index 0000000000..b0a93effb2 --- /dev/null +++ b/resources/icons/legend_shells.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_toolchanges.svg b/resources/icons/legend_toolchanges.svg new file mode 100644 index 0000000000..85b6218a9b --- /dev/null +++ b/resources/icons/legend_toolchanges.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/icons/legend_toolmarker.svg b/resources/icons/legend_toolmarker.svg new file mode 100644 index 0000000000..3cd5cf8d96 --- /dev/null +++ b/resources/icons/legend_toolmarker.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/legend_travel.svg b/resources/icons/legend_travel.svg new file mode 100644 index 0000000000..553e90a743 --- /dev/null +++ b/resources/icons/legend_travel.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/legend_wipe.svg b/resources/icons/legend_wipe.svg new file mode 100644 index 0000000000..decfcd6011 --- /dev/null +++ b/resources/icons/legend_wipe.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/resources/shaders/background.fs b/resources/shaders/background.fs new file mode 100644 index 0000000000..b148440898 --- /dev/null +++ b/resources/shaders/background.fs @@ -0,0 +1,11 @@ +#version 110 + +uniform vec4 top_color; +uniform vec4 bottom_color; + +varying vec2 tex_coord; + +void main() +{ + gl_FragColor = mix(bottom_color, top_color, tex_coord.y); +} diff --git a/resources/shaders/background.vs b/resources/shaders/background.vs new file mode 100644 index 0000000000..b7c1d92c0e --- /dev/null +++ b/resources/shaders/background.vs @@ -0,0 +1,9 @@ +#version 110 + +varying vec2 tex_coord; + +void main() +{ + gl_Position = gl_Vertex; + tex_coord = gl_MultiTexCoord0.xy; +} diff --git a/resources/shaders/flat.fs b/resources/shaders/flat.fs new file mode 100644 index 0000000000..ab656998df --- /dev/null +++ b/resources/shaders/flat.fs @@ -0,0 +1,8 @@ +#version 110 + +uniform vec4 uniform_color; + +void main() +{ + gl_FragColor = uniform_color; +} diff --git a/resources/shaders/flat.vs b/resources/shaders/flat.vs new file mode 100644 index 0000000000..d0d3ee42a9 --- /dev/null +++ b/resources/shaders/flat.vs @@ -0,0 +1,6 @@ +#version 110 + +void main() +{ + gl_Position = ftransform(); +} diff --git a/resources/shaders/flat_texture.fs b/resources/shaders/flat_texture.fs new file mode 100644 index 0000000000..ffe193b1c0 --- /dev/null +++ b/resources/shaders/flat_texture.fs @@ -0,0 +1,10 @@ +#version 110 + +uniform sampler2D uniform_texture; + +varying vec2 tex_coord; + +void main() +{ + gl_FragColor = texture2D(uniform_texture, tex_coord); +} diff --git a/resources/shaders/flat_texture.vs b/resources/shaders/flat_texture.vs new file mode 100644 index 0000000000..27addc7526 --- /dev/null +++ b/resources/shaders/flat_texture.vs @@ -0,0 +1,9 @@ +#version 110 + +varying vec2 tex_coord; + +void main() +{ + gl_Position = ftransform(); + tex_coord = gl_MultiTexCoord0.xy; +} diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 3d2e6e6b76..2648fba9e2 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -823,6 +823,35 @@ std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) co return proposed_path.string(); } +// __has_feature() is used later for Clang, this is for compatibility with other compilers (such as GCC and MSVC) +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +extern "C" { + // Based on https://github.com/google/skia/blob/main/tools/LsanSuppressions.cpp + const char *__lsan_default_suppressions() { + return "leak:libfontconfig\n" // FontConfig looks like it leaks, but it doesn't. + "leak:libfreetype\n" // Unsure, appeared upgrading Debian 9->10. + "leak:libGLX_nvidia.so\n" // For NVidia driver. + "leak:libnvidia-glcore.so\n" // For NVidia driver. + "leak:libnvidia-tls.so\n" // For NVidia driver. + "leak:terminator_CreateDevice\n" // For Intel Vulkan drivers. + ; + } +} +#endif + +#if defined(SLIC3R_UBSAN) +extern "C" { + // Enable printing stacktrace by default. It can be disabled by running PrusaSlicer with "UBSAN_OPTIONS=print_stacktrace=0". + const char *__ubsan_default_options() { + return "print_stacktrace=1"; + } +} +#endif + #if defined(_MSC_VER) || defined(__MINGW32__) extern "C" { __declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv) diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 09c80a9d9b..db0e54e60d 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -155,6 +155,18 @@ namespace ImGui const wchar_t ClippyMarker = 0x2602; const wchar_t InfoMarker = 0x2603; const wchar_t SliderFloatEditBtnIcon = 0x2604; + const wchar_t SliderFloatEditBtnPressedIcon = 0x2605; + const wchar_t LegendTravel = 0x2606; + const wchar_t LegendWipe = 0x2607; + const wchar_t LegendRetract = 0x2608; + const wchar_t LegendDeretract = 0x2609; + const wchar_t LegendSeams = 0x2610; + const wchar_t LegendToolChanges = 0x2611; + const wchar_t LegendColorChanges = 0x2612; + const wchar_t LegendPausePrints = 0x2613; + const wchar_t LegendCustomGCodes = 0x2614; + const wchar_t LegendShells = 0x2615; + const wchar_t LegendToolMarker = 0x2616; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 5b4bb34a3c..928febffd0 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -150,6 +150,11 @@ void AppConfig::set_defaults() if (get("order_volumes").empty()) set("order_volumes", "1"); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + if (get("non_manifold_edges").empty()) + set("non_manifold_edges", "1"); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + if (get("clear_undo_redo_stack_on_new_project").empty()) set("clear_undo_redo_stack_on_new_project", "1"); } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 99d010d9c7..39bc1aa474 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -14,7 +14,7 @@ if (TARGET OpenVDB::openvdb) set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp) endif() -add_library(libslic3r STATIC +set(SLIC3R_SOURCES pchheader.cpp pchheader.hpp BoundingBox.cpp @@ -29,6 +29,8 @@ add_library(libslic3r STATIC clipper.hpp ClipperUtils.cpp ClipperUtils.hpp + Color.cpp + Color.hpp Config.cpp Config.hpp EdgeGrid.cpp @@ -293,6 +295,14 @@ add_library(libslic3r STATIC SLA/ReprojectPointsOnMesh.hpp ) +add_library(libslic3r STATIC ${SLIC3R_SOURCES}) + +foreach(_source IN ITEMS ${SLIC3R_SOURCES}) + get_filename_component(_source_path "${_source}" PATH) + string(REPLACE "/" "\\" _group_path "${_source_path}") + source_group("${_group_path}" FILES "${_source}") +endforeach() + if (SLIC3R_STATIC) set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE) endif () diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp new file mode 100644 index 0000000000..7b5a7f3ba7 --- /dev/null +++ b/src/libslic3r/Color.cpp @@ -0,0 +1,400 @@ +#include "libslic3r.h" +#include "Color.hpp" + +#include + +static const float INV_255 = 1.0f / 255.0f; + +namespace Slic3r { + +// Conversion from RGB to HSV color space +// The input RGB values are in the range [0, 1] +// The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1] +static void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v) +{ + assert(0.0f <= r && r <= 1.0f); + assert(0.0f <= g && g <= 1.0f); + assert(0.0f <= b && b <= 1.0f); + + const float max_comp = std::max(std::max(r, g), b); + const float min_comp = std::min(std::min(r, g), b); + const float delta = max_comp - min_comp; + + if (delta > 0.0f) { + if (max_comp == r) + h = 60.0f * (std::fmod(((g - b) / delta), 6.0f)); + else if (max_comp == g) + h = 60.0f * (((b - r) / delta) + 2.0f); + else if (max_comp == b) + h = 60.0f * (((r - g) / delta) + 4.0f); + + s = (max_comp > 0.0f) ? delta / max_comp : 0.0f; + } + else { + h = 0.0f; + s = 0.0f; + } + v = max_comp; + + while (h < 0.0f) { h += 360.0f; } + while (h > 360.0f) { h -= 360.0f; } + + assert(0.0f <= s && s <= 1.0f); + assert(0.0f <= v && v <= 1.0f); + assert(0.0f <= h && h <= 360.0f); +} + +// Conversion from HSV to RGB color space +// The input HSV values are in the ranges h = [0, 360], and s, v = [0, 1] +// The output RGB values are in the range [0, 1] +static void HSVtoRGB(float h, float s, float v, float& r, float& g, float& b) +{ + assert(0.0f <= s && s <= 1.0f); + assert(0.0f <= v && v <= 1.0f); + assert(0.0f <= h && h <= 360.0f); + + const float chroma = v * s; + const float h_prime = std::fmod(h / 60.0f, 6.0f); + const float x = chroma * (1.0f - std::abs(std::fmod(h_prime, 2.0f) - 1.0f)); + const float m = v - chroma; + + if (0.0f <= h_prime && h_prime < 1.0f) { + r = chroma; + g = x; + b = 0.0f; + } + else if (1.0f <= h_prime && h_prime < 2.0f) { + r = x; + g = chroma; + b = 0.0f; + } + else if (2.0f <= h_prime && h_prime < 3.0f) { + r = 0.0f; + g = chroma; + b = x; + } + else if (3.0f <= h_prime && h_prime < 4.0f) { + r = 0.0f; + g = x; + b = chroma; + } + else if (4.0f <= h_prime && h_prime < 5.0f) { + r = x; + g = 0.0f; + b = chroma; + } + else if (5.0f <= h_prime && h_prime < 6.0f) { + r = chroma; + g = 0.0f; + b = x; + } + else { + r = 0.0f; + g = 0.0f; + b = 0.0f; + } + + r += m; + g += m; + b += m; + + assert(0.0f <= r && r <= 1.0f); + assert(0.0f <= g && g <= 1.0f); + assert(0.0f <= b && b <= 1.0f); +} + +class Randomizer +{ + std::random_device m_rd; + +public: + float random_float(float min, float max) { + std::mt19937 rand_generator(m_rd()); + std::uniform_real_distribution distrib(min, max); + return distrib(rand_generator); + } +}; + +ColorRGB::ColorRGB(float r, float g, float b) +: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) }) +{ +} + +ColorRGB::ColorRGB(unsigned char r, unsigned char g, unsigned char b) +: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f) }) +{ +} + +bool ColorRGB::operator < (const ColorRGB& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] < other.m_data[i]) + return true; + } + + return false; +} + +bool ColorRGB::operator > (const ColorRGB& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] > other.m_data[i]) + return true; + } + + return false; +} + +ColorRGB ColorRGB::operator + (const ColorRGB& other) const +{ + ColorRGB ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGB ColorRGB::operator * (float value) const +{ + assert(value >= 0.0f); + ColorRGB ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGBA::ColorRGBA(float r, float g, float b, float a) +: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f), std::clamp(a, 0.0f, 1.0f) }) +{ +} + +ColorRGBA::ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f), std::clamp(a * INV_255, 0.0f, 1.0f) }) +{ +} + +bool ColorRGBA::operator < (const ColorRGBA& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] < other.m_data[i]) + return true; + } + + return false; +} + +bool ColorRGBA::operator > (const ColorRGBA& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] > other.m_data[i]) + return true; + } + + return false; +} + +ColorRGBA ColorRGBA::operator + (const ColorRGBA& other) const +{ + ColorRGBA ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGBA ColorRGBA::operator * (float value) const +{ + assert(value >= 0.0f); + ColorRGBA ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); + } + ret.m_data[3] = this->m_data[3]; + return ret; +} + +ColorRGB operator * (float value, const ColorRGB& other) { return other * value; } +ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; } + +ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t) +{ + assert(0.0f <= t && t <= 1.0f); + return (1.0f - t) * a + t * b; +} + +ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t) +{ + assert(0.0f <= t && t <= 1.0f); + return (1.0f - t) * a + t * b; +} + +ColorRGB complementary(const ColorRGB& color) +{ + return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b() }; +} + +ColorRGBA complementary(const ColorRGBA& color) +{ + return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b(), color.a() }; +} + +ColorRGB saturate(const ColorRGB& color, float factor) +{ + float h, s, v; + RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); + s = std::clamp(s * factor, 0.0f, 1.0f); + float r, g, b; + HSVtoRGB(h, s, v, r, g, b); + return { r, g, b }; +} + +ColorRGBA saturate(const ColorRGBA& color, float factor) +{ + return to_rgba(saturate(to_rgb(color), factor), color.a()); +} + +ColorRGB opposite(const ColorRGB& color) +{ + float h, s, v; + RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); + + h += 65.0f; // 65 instead 60 to avoid circle values + if (h > 360.0f) + h -= 360.0f; + + Randomizer rnd; + s = rnd.random_float(0.65f, 1.0f); + v = rnd.random_float(0.65f, 1.0f); + + float r, g, b; + HSVtoRGB(h, s, v, r, g, b); + return { r, g, b }; +} + +ColorRGB opposite(const ColorRGB& a, const ColorRGB& b) +{ + float ha, sa, va; + RGBtoHSV(a.r(), a.g(), a.b(), ha, sa, va); + float hb, sb, vb; + RGBtoHSV(b.r(), b.g(), b.b(), hb, sb, vb); + + float delta_h = std::abs(ha - hb); + float start_h = (delta_h > 180.0f) ? std::min(ha, hb) : std::max(ha, hb); + + start_h += 5.0f; // to avoid circle change of colors for 120 deg + if (delta_h < 180.0f) + delta_h = 360.0f - delta_h; + + Randomizer rnd; + float out_h = start_h + 0.5f * delta_h; + if (out_h > 360.0f) + out_h -= 360.0f; + float out_s = rnd.random_float(0.65f, 1.0f); + float out_v = rnd.random_float(0.65f, 1.0f); + + float out_r, out_g, out_b; + HSVtoRGB(out_h, out_s, out_v, out_r, out_g, out_b); + return { out_r, out_g, out_b }; +} + +bool can_decode_color(const std::string& color) { return color.size() == 7 && color.front() == '#'; } + +bool decode_color(const std::string& color_in, ColorRGB& color_out) +{ + auto hex_digit_to_int = [](const char c) { + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; + }; + + color_out = ColorRGB::BLACK(); + if (can_decode_color(color_in)) { + const char* c = color_in.data() + 1; + for (unsigned int i = 0; i < 3; ++i) { + const int digit1 = hex_digit_to_int(*c++); + const int digit2 = hex_digit_to_int(*c++); + if (digit1 != -1 && digit2 != -1) + color_out.set(i, float(digit1 * 16 + digit2) * INV_255); + } + } + else + return false; + + assert(0.0f <= color_out.r() && color_out.r() <= 1.0f); + assert(0.0f <= color_out.g() && color_out.g() <= 1.0f); + assert(0.0f <= color_out.b() && color_out.b() <= 1.0f); + return true; +} + +bool decode_color(const std::string& color_in, ColorRGBA& color_out) +{ + ColorRGB rgb; + if (!decode_color(color_in, rgb)) + return false; + + color_out = to_rgba(rgb, color_out.a()); + return true; +} + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out) +{ + colors_out = std::vector(colors_in.size(), ColorRGB::BLACK()); + for (size_t i = 0; i < colors_in.size(); ++i) { + if (!decode_color(colors_in[i], colors_out[i])) + return false; + } + return true; +} + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out) +{ + colors_out = std::vector(colors_in.size(), ColorRGBA::BLACK()); + for (size_t i = 0; i < colors_in.size(); ++i) { + if (!decode_color(colors_in[i], colors_out[i])) + return false; + } + return true; +} + +std::string encode_color(const ColorRGB& color) +{ + char buffer[64]; + ::sprintf(buffer, "#%02X%02X%02X", color.r_uchar(), color.g_uchar(), color.b_uchar()); + return std::string(buffer); +} + +std::string encode_color(const ColorRGBA& color) { return encode_color(to_rgb(color)); } + +ColorRGB to_rgb(const ColorRGBA& other_rgba) { return { other_rgba.r(), other_rgba.g(), other_rgba.b() }; } +ColorRGBA to_rgba(const ColorRGB& other_rgb) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), 1.0f }; } +ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), alpha }; } + +ColorRGBA picking_decode(unsigned int id) +{ + return { + float((id >> 0) & 0xff) * INV_255, // red + float((id >> 8) & 0xff) * INV_255, // green + float((id >> 16) & 0xff) * INV_255, // blue + float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff)) * INV_255 // checksum for validating against unwanted alpha blending and multi sampling + }; +} + +unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b) { return r + (g << 8) + (b << 16); } + +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) +{ + // 8 bit hash for the color + unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; + // Increase enthropy by a bit reversal + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + // Flip every second bit to increase the enthropy even more. + b ^= 0x55; + return b; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp new file mode 100644 index 0000000000..183705c4af --- /dev/null +++ b/src/libslic3r/Color.hpp @@ -0,0 +1,174 @@ +#ifndef slic3r_Color_hpp_ +#define slic3r_Color_hpp_ + +#include +#include + +namespace Slic3r { + +class ColorRGB +{ + std::array m_data{1.0f, 1.0f, 1.0f}; + +public: + ColorRGB() = default; + ColorRGB(float r, float g, float b); + ColorRGB(unsigned char r, unsigned char g, unsigned char b); + ColorRGB(const ColorRGB& other) = default; + + ColorRGB& operator = (const ColorRGB& other) { m_data = other.m_data; return *this; } + + bool operator == (const ColorRGB& other) const { return m_data == other.m_data; } + bool operator != (const ColorRGB& other) const { return !operator==(other); } + bool operator < (const ColorRGB& other) const; + bool operator > (const ColorRGB& other) const; + + ColorRGB operator + (const ColorRGB& other) const; + ColorRGB operator * (float value) const; + + const float* const data() const { return m_data.data(); } + + float r() const { return m_data[0]; } + float g() const { return m_data[1]; } + float b() const { return m_data[2]; } + + void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); } + void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } + void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } + + void set(unsigned int comp, float value) { + assert(0 <= comp && comp <= 2); + m_data[comp] = std::clamp(value, 0.0f, 1.0f); + } + + unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); } + + static const ColorRGB BLACK() { return { 0.0f, 0.0f, 0.0f }; } + static const ColorRGB BLUE() { return { 0.0f, 0.0f, 1.0f }; } + static const ColorRGB BLUEISH() { return { 0.5f, 0.5f, 1.0f }; } + static const ColorRGB CYAN() { return { 0.0f, 1.0f, 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 MAGENTA() { return { 1.0f, 0.0f, 1.0f }; } + static const ColorRGB ORANGE() { return { 0.92f, 0.50f, 0.26f }; } + static const ColorRGB RED() { return { 1.0f, 0.0f, 0.0f }; } + static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; } + static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; } + static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; } + + static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; } + static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; } + static const ColorRGB Z() { return { 0.0f, 0.0f, 0.75f }; } +}; + +class ColorRGBA +{ + std::array m_data{ 1.0f, 1.0f, 1.0f, 1.0f }; + +public: + ColorRGBA() = default; + ColorRGBA(float r, float g, float b, float a); + ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + ColorRGBA(const ColorRGBA& other) = default; + + ColorRGBA& operator = (const ColorRGBA& other) { m_data = other.m_data; return *this; } + + bool operator == (const ColorRGBA& other) const { return m_data == other.m_data; } + bool operator != (const ColorRGBA& other) const { return !operator==(other); } + bool operator < (const ColorRGBA& other) const; + bool operator > (const ColorRGBA& other) const; + + ColorRGBA operator + (const ColorRGBA& other) const; + ColorRGBA operator * (float value) const; + + const float* const data() const { return m_data.data(); } + + float r() const { return m_data[0]; } + float g() const { return m_data[1]; } + float b() const { return m_data[2]; } + float a() const { return m_data[3]; } + + void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); } + void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } + void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } + void a(float a) { m_data[3] = std::clamp(a, 0.0f, 1.0f); } + + void set(unsigned int comp, float value) { + assert(0 <= comp && comp <= 3); + m_data[comp] = std::clamp(value, 0.0f, 1.0f); + } + + unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); } + unsigned char a_uchar() const { return static_cast(m_data[3] * 255.0f); } + + bool is_transparent() const { return m_data[3] < 1.0f; } + + static const ColorRGBA BLACK() { return { 0.0f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA BLUE() { return { 0.0f, 0.0f, 1.0f, 1.0f }; } + static const ColorRGBA BLUEISH() { return { 0.5f, 0.5f, 1.0f, 1.0f }; } + static const ColorRGBA CYAN() { return { 0.0f, 1.0f, 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 MAGENTA() { return { 1.0f, 0.0f, 1.0f, 1.0f }; } + static const ColorRGBA ORANGE() { return { 0.923f, 0.504f, 0.264f, 1.0f }; } + static const ColorRGBA RED() { return { 1.0f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA REDISH() { return { 1.0f, 0.5f, 0.5f, 1.0f }; } + static const ColorRGBA YELLOW() { return { 1.0f, 1.0f, 0.0f, 1.0f }; } + static const ColorRGBA WHITE() { return { 1.0f, 1.0f, 1.0f, 1.0f }; } + + static const ColorRGBA X() { return { 0.75f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA Y() { return { 0.0f, 0.75f, 0.0f, 1.0f }; } + static const ColorRGBA Z() { return { 0.0f, 0.0f, 0.75f, 1.0f }; } +}; + +extern ColorRGB operator * (float value, const ColorRGB& other); +extern ColorRGBA operator * (float value, const ColorRGBA& other); + +extern ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t); +extern ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t); + +extern ColorRGB complementary(const ColorRGB& color); +extern ColorRGBA complementary(const ColorRGBA& color); + +extern ColorRGB saturate(const ColorRGB& color, float factor); +extern ColorRGBA saturate(const ColorRGBA& color, float factor); + +extern ColorRGB opposite(const ColorRGB& color); +extern ColorRGB opposite(const ColorRGB& a, const ColorRGB& b); + +extern bool can_decode_color(const std::string& color); + +extern bool decode_color(const std::string& color_in, ColorRGB& color_out); +extern bool decode_color(const std::string& color_in, ColorRGBA& color_out); + +extern bool decode_colors(const std::vector& colors_in, std::vector& colors_out); +extern bool decode_colors(const std::vector& colors_in, std::vector& colors_out); + +extern std::string encode_color(const ColorRGB& color); +extern std::string encode_color(const ColorRGBA& color); + +extern ColorRGB to_rgb(const ColorRGBA& other_rgba); +extern ColorRGBA to_rgba(const ColorRGB& other_rgb); +extern ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha); + +extern ColorRGBA picking_decode(unsigned int id); +extern unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b); +// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components +// were not interpolated by alpha blending or multi sampling. +extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); + +} // namespace Slic3r + +#endif /* slic3r_Color_hpp_ */ diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 391ac2d587..e6ae1edd4d 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -56,12 +56,9 @@ ExtrusionEntityCollection::operator ExtrusionPaths() const return paths; } -ExtrusionEntity* ExtrusionEntityCollection::clone() const +ExtrusionEntity *ExtrusionEntityCollection::clone() const { - ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this); - for (size_t i = 0; i < coll->entities.size(); ++i) - coll->entities[i] = this->entities[i]->clone(); - return coll; + return new ExtrusionEntityCollection(*this); } void ExtrusionEntityCollection::reverse() diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 69476175e0..7d868860a7 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -2391,7 +2391,7 @@ static std::vector chain_monotonic_regions( // Probability (unnormalized) of traversing a link between two monotonic regions. auto path_probability = [ -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(__clang__) // clang complains when capturing constexpr constants. pheromone_alpha, pheromone_beta #endif // __APPLE__ diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 235cdecb5b..a8b66b15b1 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -283,25 +283,28 @@ void AMFParserContext::startElement(const char *name, const char **atts) m_value[0] = type; node_type_new = NODE_TYPE_METADATA; } - } else if (strcmp(name, "material") == 0) { + } + else if (strcmp(name, "material") == 0) { const char *material_id = get_attribute(atts, "id"); m_material = m_model.add_material((material_id == nullptr) ? "_" : material_id); node_type_new = NODE_TYPE_MATERIAL; - } else if (strcmp(name, "object") == 0) { + } + else if (strcmp(name, "object") == 0) { const char *object_id = get_attribute(atts, "id"); if (object_id == nullptr) this->stop(); else { assert(m_object_vertices.empty()); m_object = m_model.add_object(); + m_object->name = std::string(object_id); m_object_instances_map[object_id].idx = int(m_model.objects.size())-1; node_type_new = NODE_TYPE_OBJECT; } - } else if (strcmp(name, "constellation") == 0) { - node_type_new = NODE_TYPE_CONSTELLATION; - } else if (strcmp(name, "custom_gcodes_per_height") == 0) { - node_type_new = NODE_TYPE_CUSTOM_GCODE; } + else if (strcmp(name, "constellation") == 0) + node_type_new = NODE_TYPE_CONSTELLATION; + else if (strcmp(name, "custom_gcodes_per_height") == 0) + node_type_new = NODE_TYPE_CUSTOM_GCODE; break; case 2: if (strcmp(name, "metadata") == 0) { @@ -309,12 +312,14 @@ void AMFParserContext::startElement(const char *name, const char **atts) m_value[0] = get_attribute(atts, "type"); node_type_new = NODE_TYPE_METADATA; } - } else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT) + } + else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT) node_type_new = NODE_TYPE_LAYER_CONFIG; else if (strcmp(name, "mesh") == 0) { if (m_path[1] == NODE_TYPE_OBJECT) node_type_new = NODE_TYPE_MESH; - } else if (strcmp(name, "instance") == 0) { + } + else if (strcmp(name, "instance") == 0) { if (m_path[1] == NODE_TYPE_CONSTELLATION) { const char *object_id = get_attribute(atts, "objectid"); if (object_id == nullptr) @@ -910,12 +915,17 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut if (result) ctx.endDocument(); - for (ModelObject* o : model->objects) - { - for (ModelVolume* v : o->volumes) - { - if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART)) + for (ModelObject* o : model->objects) { + unsigned int counter = 0; + for (ModelVolume* v : o->volumes) { + ++counter; + if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) v->source.input_file = path; + if (v->name.empty()) { + v->name = o->name; + if (o->volumes.size() > 1) + v->name += "_" + std::to_string(counter); + } } } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3e7478c05b..995964eb5f 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -189,6 +189,9 @@ void GCodeProcessor::TimeMachine::reset() max_travel_acceleration = 0.0f; extrude_factor_override_percentage = 1.0f; time = 0.0f; +#if ENABLE_TRAVEL_TIME + travel_time = 0.0f; +#endif // ENABLE_TRAVEL_TIME stop_times = std::vector(); curr.reset(); prev.reset(); @@ -304,9 +307,17 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, floa block_time += additional_time; time += block_time; +#if ENABLE_TRAVEL_TIME + if (block.move_type == EMoveType::Travel) + travel_time += block_time; + else + roles_time[static_cast(block.role)] += block_time; +#endif // ENABLE_TRAVEL_TIME gcode_time.cache += block_time; moves_time[static_cast(block.move_type)] += block_time; +#if !ENABLE_TRAVEL_TIME roles_time[static_cast(block.role)] += block_time; +#endif // !ENABLE_TRAVEL_TIME if (block.layer_id >= layers_time.size()) { const size_t curr_size = layers_time.size(); layers_time.resize(block.layer_id); @@ -1177,6 +1188,7 @@ void GCodeProcessor::reset() m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f }; m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_saved_position = { 0.0f, 0.0f, 0.0f, 0.0f }; m_origin = { 0.0f, 0.0f, 0.0f, 0.0f }; m_cached_position.reset(); m_wiping = false; @@ -1378,6 +1390,18 @@ std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mod return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); } +#if ENABLE_TRAVEL_TIME +float GCodeProcessor::get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].travel_time : 0.0f; +} + +std::string GCodeProcessor::get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].travel_time)) : std::string("N/A"); +} +#endif // ENABLE_TRAVEL_TIME + std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const { std::vector>> ret; @@ -1593,6 +1617,13 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool default: break; } break; + case '6': + switch (cmd[2]) { + case '0': { process_G60(line); break; } // Save Current Position + case '1': { process_G61(line); break; } // Return to Saved Position + default: break; + } + break; case '9': switch (cmd[2]) { case '0': { process_G90(line); break; } // Set to Absolute Positioning @@ -2805,6 +2836,43 @@ void GCodeProcessor::process_G28(const GCodeReader::GCodeLine& line) process_G1(new_gline); } +void GCodeProcessor::process_G60(const GCodeReader::GCodeLine& line) +{ + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) + m_saved_position = m_end_position; +} + +void GCodeProcessor::process_G61(const GCodeReader::GCodeLine& line) +{ + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) { + bool modified = false; + if (line.has_x()) { + m_end_position[X] = m_saved_position[X]; + modified = true; + } + if (line.has_y()) { + m_end_position[Y] = m_saved_position[Y]; + modified = true; + } + if (line.has_z()) { + m_end_position[Z] = m_saved_position[Z]; + modified = true; + } + if (line.has_e()) { + m_end_position[E] = m_saved_position[E]; + modified = true; + } + if (line.has_f()) + m_feedrate = line.f(); + + if (!modified) + m_end_position = m_saved_position; + + + store_move_vertex(EMoveType::Travel); + } +} + void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line) { m_global_positioning_type = EPositioningType::Absolute; @@ -3395,6 +3463,9 @@ void GCodeProcessor::update_estimated_times_stats() auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; data.time = get_time(mode); +#if ENABLE_TRAVEL_TIME + data.travel_time = get_travel_time(mode); +#endif // ENABLE_TRAVEL_TIME data.custom_gcode_times = get_custom_gcode_times(mode, true); data.moves_times = get_moves_time(mode); data.roles_times = get_roles_time(mode); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 5132825e50..153f4a9c5c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -44,6 +44,9 @@ namespace Slic3r { struct Mode { float time; +#if ENABLE_TRAVEL_TIME + float travel_time; +#endif // ENABLE_TRAVEL_TIME std::vector>> custom_gcode_times; std::vector> moves_times; std::vector> roles_times; @@ -51,6 +54,9 @@ namespace Slic3r { void reset() { time = 0.0f; +#if ENABLE_TRAVEL_TIME + travel_time = 0.0f; +#endif // ENABLE_TRAVEL_TIME custom_gcode_times.clear(); moves_times.clear(); roles_times.clear(); @@ -290,6 +296,9 @@ namespace Slic3r { float max_travel_acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s +#if ENABLE_TRAVEL_TIME + float travel_time; // s +#endif // ENABLE_TRAVEL_TIME struct StopTime { unsigned int g1_line_id; @@ -508,6 +517,7 @@ namespace Slic3r { AxisCoords m_start_position; // mm AxisCoords m_end_position; // mm + AxisCoords m_saved_position; // mm AxisCoords m_origin; // mm CachedPosition m_cached_position; bool m_wiping; @@ -599,6 +609,10 @@ namespace Slic3r { float get_time(PrintEstimatedStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; +#if ENABLE_TRAVEL_TIME + float get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::string get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; +#endif // ENABLE_TRAVEL_TIME std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; @@ -648,6 +662,12 @@ namespace Slic3r { // Move to origin void process_G28(const GCodeReader::GCodeLine& line); + // Save Current Position + void process_G60(const GCodeReader::GCodeLine& line); + + // Return to Saved Position + void process_G61(const GCodeReader::GCodeLine& line); + // Set to Absolute Positioning void process_G90(const GCodeReader::GCodeLine& line); diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index ecb29d4b81..00fac6c381 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -325,40 +325,36 @@ Vec3d extract_euler_angles(const Eigen::Matrix& // reference: http://www.gregslabaugh.net/publications/euler.pdf Vec3d angles1 = Vec3d::Zero(); Vec3d angles2 = Vec3d::Zero(); - if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5) - { - angles1(2) = 0.0; - if (rotation_matrix(2, 0) < 0.0) // == -1.0 - { - angles1(1) = 0.5 * (double)PI; - angles1(0) = angles1(2) + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); + if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5) { + angles1.z() = 0.0; + if (rotation_matrix(2, 0) < 0.0) { // == -1.0 + angles1.y() = 0.5 * double(PI); + angles1.x() = angles1.z() + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); } - else // == 1.0 - { - angles1(1) = - 0.5 * (double)PI; - angles1(0) = - angles1(2) + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2)); + else { // == 1.0 + angles1.y() = - 0.5 * double(PI); + angles1.x() = - angles1.y() + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2)); } angles2 = angles1; } - else - { - angles1(1) = -::asin(rotation_matrix(2, 0)); - double inv_cos1 = 1.0 / ::cos(angles1(1)); - angles1(0) = ::atan2(rotation_matrix(2, 1) * inv_cos1, rotation_matrix(2, 2) * inv_cos1); - angles1(2) = ::atan2(rotation_matrix(1, 0) * inv_cos1, rotation_matrix(0, 0) * inv_cos1); + else { + angles1.y() = -::asin(rotation_matrix(2, 0)); + const double inv_cos1 = 1.0 / ::cos(angles1.y()); + angles1.x() = ::atan2(rotation_matrix(2, 1) * inv_cos1, rotation_matrix(2, 2) * inv_cos1); + angles1.z() = ::atan2(rotation_matrix(1, 0) * inv_cos1, rotation_matrix(0, 0) * inv_cos1); - angles2(1) = (double)PI - angles1(1); - double inv_cos2 = 1.0 / ::cos(angles2(1)); - angles2(0) = ::atan2(rotation_matrix(2, 1) * inv_cos2, rotation_matrix(2, 2) * inv_cos2); - angles2(2) = ::atan2(rotation_matrix(1, 0) * inv_cos2, rotation_matrix(0, 0) * inv_cos2); + angles2.y() = double(PI) - angles1.y(); + const double inv_cos2 = 1.0 / ::cos(angles2.y()); + angles2.x() = ::atan2(rotation_matrix(2, 1) * inv_cos2, rotation_matrix(2, 2) * inv_cos2); + angles2.z() = ::atan2(rotation_matrix(1, 0) * inv_cos2, rotation_matrix(0, 0) * inv_cos2); } // The following euristic is the best found up to now (in the sense that it works fine with the greatest number of edge use-cases) // but there are other use-cases were it does not // We need to improve it - double min_1 = angles1.cwiseAbs().minCoeff(); - double min_2 = angles2.cwiseAbs().minCoeff(); - bool use_1 = (min_1 < min_2) || (is_approx(min_1, min_2) && (angles1.norm() <= angles2.norm())); + const double min_1 = angles1.cwiseAbs().minCoeff(); + const double min_2 = angles2.cwiseAbs().minCoeff(); + const bool use_1 = (min_1 < min_2) || (is_approx(min_1, min_2) && (angles1.norm() <= angles2.norm())); return use_1 ? angles1 : angles2; } @@ -374,14 +370,6 @@ Vec3d extract_euler_angles(const Transform3d& transform) return extract_euler_angles(m); } -Transformation::Flags::Flags() - : dont_translate(true) - , dont_rotate(true) - , dont_scale(true) - , dont_mirror(true) -{ -} - bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror); @@ -407,15 +395,14 @@ Transformation::Transformation(const Transform3d& transform) void Transformation::set_offset(const Vec3d& offset) { - set_offset(X, offset(0)); - set_offset(Y, offset(1)); - set_offset(Z, offset(2)); + set_offset(X, offset.x()); + set_offset(Y, offset.y()); + set_offset(Z, offset.z()); } void Transformation::set_offset(Axis axis, double offset) { - if (m_offset(axis) != offset) - { + if (m_offset(axis) != offset) { m_offset(axis) = offset; m_dirty = true; } @@ -423,19 +410,18 @@ void Transformation::set_offset(Axis axis, double offset) void Transformation::set_rotation(const Vec3d& rotation) { - set_rotation(X, rotation(0)); - set_rotation(Y, rotation(1)); - set_rotation(Z, rotation(2)); + set_rotation(X, rotation.x()); + set_rotation(Y, rotation.y()); + set_rotation(Z, rotation.z()); } void Transformation::set_rotation(Axis axis, double rotation) { rotation = angle_to_0_2PI(rotation); - if (is_approx(std::abs(rotation), 2.0 * (double)PI)) + if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; - if (m_rotation(axis) != rotation) - { + if (m_rotation(axis) != rotation) { m_rotation(axis) = rotation; m_dirty = true; } @@ -443,15 +429,14 @@ void Transformation::set_rotation(Axis axis, double rotation) void Transformation::set_scaling_factor(const Vec3d& scaling_factor) { - set_scaling_factor(X, scaling_factor(0)); - set_scaling_factor(Y, scaling_factor(1)); - set_scaling_factor(Z, scaling_factor(2)); + set_scaling_factor(X, scaling_factor.x()); + set_scaling_factor(Y, scaling_factor.y()); + set_scaling_factor(Z, scaling_factor.z()); } void Transformation::set_scaling_factor(Axis axis, double scaling_factor) { - if (m_scaling_factor(axis) != std::abs(scaling_factor)) - { + if (m_scaling_factor(axis) != std::abs(scaling_factor)) { m_scaling_factor(axis) = std::abs(scaling_factor); m_dirty = true; } @@ -459,9 +444,9 @@ void Transformation::set_scaling_factor(Axis axis, double scaling_factor) void Transformation::set_mirror(const Vec3d& mirror) { - set_mirror(X, mirror(0)); - set_mirror(Y, mirror(1)); - set_mirror(Z, mirror(2)); + set_mirror(X, mirror.x()); + set_mirror(Y, mirror.y()); + set_mirror(Z, mirror.z()); } void Transformation::set_mirror(Axis axis, double mirror) @@ -472,8 +457,7 @@ void Transformation::set_mirror(Axis axis, double mirror) else if (abs_mirror != 1.0) mirror /= abs_mirror; - if (m_mirror(axis) != mirror) - { + if (m_mirror(axis) != mirror) { m_mirror(axis) = mirror; m_dirty = true; } @@ -491,9 +475,8 @@ void Transformation::set_from_transform(const Transform3d& transform) // we can only detect if the matrix contains a left handed reference system // in which case we reorient it back to right handed by mirroring the x axis Vec3d mirror = Vec3d::Ones(); - if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) - { - mirror(0) = -1.0; + if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) { + mirror.x() = -1.0; // remove mirror m3x3.col(0) *= -1.0; } @@ -530,8 +513,7 @@ void Transformation::reset() const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { - if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) - { + if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) { m_matrix = Geometry::assemble_transform( dont_translate ? Vec3d::Zero() : m_offset, dont_rotate ? Vec3d::Zero() : m_rotation, @@ -560,8 +542,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation // Just set the inverse. out.set_from_transform(instance_transformation.get_matrix(true).inverse()); } - else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) - { + else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) { // Anisotropic scaling, rotation by multiples of ninety degrees. Eigen::Matrix3d instance_rotation_trafo = (Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) * @@ -594,8 +575,8 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i)); out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo)); - out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2)))); - out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1)); + out.set_scaling_factor(Vec3d(std::abs(scale.x()), std::abs(scale.y()), std::abs(scale.z()))); + out.set_mirror(Vec3d(scale.x() > 0 ? 1. : -1, scale.y() > 0 ? 1. : -1, scale.z() > 0 ? 1. : -1)); } else { @@ -614,19 +595,15 @@ Transform3d transform3d_from_string(const std::string& transform_str) assert(is_decimal_separator_point()); // for atof Transform3d transform = Transform3d::Identity(); - if (!transform_str.empty()) - { + if (!transform_str.empty()) { std::vector mat_elements_str; boost::split(mat_elements_str, transform_str, boost::is_any_of(" "), boost::token_compress_on); - unsigned int size = (unsigned int)mat_elements_str.size(); - if (size == 16) - { + const unsigned int size = (unsigned int)mat_elements_str.size(); + if (size == 16) { unsigned int i = 0; - for (unsigned int r = 0; r < 4; ++r) - { - for (unsigned int c = 0; c < 4; ++c) - { + for (unsigned int r = 0; r < 4; ++r) { + for (unsigned int c = 0; c < 4; ++c) { transform(r, c) = ::atof(mat_elements_str[i++].c_str()); } } @@ -640,17 +617,17 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot { return // From the current coordinate system to world. - Eigen::AngleAxisd(rot_xyz_to(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to(0), Vec3d::UnitX()) * + Eigen::AngleAxisd(rot_xyz_to.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to.x(), Vec3d::UnitX()) * // From world to the initial coordinate system. - Eigen::AngleAxisd(-rot_xyz_from(0), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from(1), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from(2), Vec3d::UnitZ()); + Eigen::AngleAxisd(-rot_xyz_from.x(), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from.z(), Vec3d::UnitZ()); } // This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { - Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - Vec3d axis = angle_axis.axis(); - double angle = angle_axis.angle(); + const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + const Vec3d axis = angle_axis.axis(); + const double angle = angle_axis.angle(); #ifndef NDEBUG if (std::abs(angle) > 1e-8) { assert(std::abs(axis.x()) < 1e-8); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index e84a7aace9..c825f82544 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -347,25 +347,23 @@ class Transformation { struct Flags { - bool dont_translate; - bool dont_rotate; - bool dont_scale; - bool dont_mirror; - - Flags(); + bool dont_translate{ true }; + bool dont_rotate{ true }; + bool dont_scale{ true }; + bool dont_mirror{ true }; bool needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const; void set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror); }; - Vec3d m_offset; // In unscaled coordinates - Vec3d m_rotation; // Rotation around the three axes, in radians around mesh center point - Vec3d m_scaling_factor; // Scaling factors along the three axes - Vec3d m_mirror; // Mirroring along the three axes + Vec3d m_offset{ Vec3d::Zero() }; // In unscaled coordinates + Vec3d m_rotation{ Vec3d::Zero() }; // Rotation around the three axes, in radians around mesh center point + Vec3d m_scaling_factor{ Vec3d::Ones() }; // Scaling factors along the three axes + Vec3d m_mirror{ Vec3d::Ones() }; // Mirroring along the three axes - mutable Transform3d m_matrix; + mutable Transform3d m_matrix{ Transform3d::Identity() }; mutable Flags m_flags; - mutable bool m_dirty; + mutable bool m_dirty{ false }; public: Transformation(); @@ -435,7 +433,7 @@ extern double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to // Is the angle close to a multiple of 90 degrees? inline bool is_rotation_ninety_degrees(double a) { - a = fmod(std::abs(a), 0.5 * M_PI); + a = fmod(std::abs(a), 0.5 * PI); if (a > 0.25 * PI) a = 0.5 * PI - a; return a < 0.001; diff --git a/src/libslic3r/Int128.hpp b/src/libslic3r/Int128.hpp index 8dc9e012dd..7aeacbfcee 100644 --- a/src/libslic3r/Int128.hpp +++ b/src/libslic3r/Int128.hpp @@ -105,6 +105,11 @@ public: static inline Int128 multiply(int64_t lhs, int64_t rhs) { return Int128(__int128(lhs) * __int128(rhs)); } +#if defined(__clang__) + // When Clang is used with enabled UndefinedBehaviorSanitizer, it produces "undefined reference to '__muloti4'" when __int128 is used. + // Because of that, UndefinedBehaviorSanitizer is disabled for this function. + __attribute__((no_sanitize("undefined"))) +#endif // Evaluate signum of a 2x2 determinant. static int sign_determinant_2x2(int64_t a11, int64_t a12, int64_t a21, int64_t a22) { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index efc66f4781..67450fb116 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2264,7 +2264,7 @@ void check_model_ids_validity(const Model &model) for (const ModelInstance *model_instance : model_object->instances) check(model_instance->id()); } - for (const auto mm : model.materials) { + for (const auto &mm : model.materials) { check(mm.second->id()); check(mm.second->config.id()); } diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 935348279c..b5208b1d8b 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -17,7 +17,8 @@ class MultiPoint public: Points points; - MultiPoint() {} + MultiPoint() = default; + virtual ~MultiPoint() = default; MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} MultiPoint(std::initializer_list list) : points(list) {} diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index f763c8bb6c..e84ddc5035 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -501,6 +501,7 @@ namespace client boost::throw_exception(qi::expectation_failure( lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); } + lhs.reset(); lhs.type = TYPE_BOOL; lhs.data.b = invert ? ! value : value; } diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 7d34e3aaec..d245403392 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -19,7 +19,7 @@ class Polygon : public MultiPoint { public: Polygon() = default; - virtual ~Polygon() = default; + ~Polygon() override = default; explicit Polygon(const Points &points) : MultiPoint(points) {} Polygon(std::initializer_list points) : MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 31e0b88d32..5766d9671b 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -16,7 +16,8 @@ typedef std::vector ThickPolylines; class Polyline : public MultiPoint { public: - Polyline() {}; + Polyline() = default; + ~Polyline() override = default; Polyline(const Polyline &other) : MultiPoint(other.points) {} Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} Polyline(std::initializer_list list) : MultiPoint(list) {} diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index f4e4c14717..fc45110fd4 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -116,6 +116,8 @@ public: // This type is here to support PresetConfigSubstitutions for physical printers, however it does not belong to the Preset class, // PhysicalPrinter class is used instead. TYPE_PHYSICAL_PRINTER, + // This type is here to support search through the Preferences + TYPE_PREFERENCES, }; Type type = TYPE_INVALID; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index c2babd53cc..b6e4802bbb 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -346,8 +346,12 @@ private: friend class Print; PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances); - ~PrintObject() { if (m_shared_regions && -- m_shared_regions->m_ref_cnt == 0) delete m_shared_regions; } - + ~PrintObject() { + if (m_shared_regions && --m_shared_regions->m_ref_cnt == 0) + delete m_shared_regions; + clear_layers(); + } + void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); } void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { m_config.apply_only(other, keys, ignore_nonexistent); } PrintBase::ApplyStatus set_instances(PrintInstances &&instances); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b1a25829b1..1aaf8c1dde 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2759,7 +2759,8 @@ void PrintConfigDef::init_fff_params() def->label = L("Synchronize with object layers"); def->category = L("Support material"); def->tooltip = L("Synchronize support layers with the object print layers. This is useful " - "with multi-material printers, where the extruder switch is expensive."); + "with multi-material printers, where the extruder switch is expensive. " + "This option is only available when top contact Z distance is set to zero."); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index 2243a3c1b5..bc68cd3778 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -3,7 +3,6 @@ #include #include "libslic3r/ExPolygon.hpp" -#include "libslic3r/MTUtils.hpp" // For rasterizing #include diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index 581e84880b..cc9aca0274 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -77,6 +77,8 @@ std::unique_ptr create_raster_grayscale_aa( if (gamma > 0) rst = std::make_unique(res, pxdim, tr, gamma); + else if (std::abs(gamma - 1.) < 1e-6) + rst = std::make_unique(res, pxdim, tr, agg::gamma_none()); else rst = std::make_unique(res, pxdim, tr, agg::gamma_threshold(.5)); diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 1eba360e31..6439830fe8 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -9,7 +9,7 @@ #include #include -#include +//#include namespace Slic3r { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 833146985a..0622bec4e5 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -10,6 +10,8 @@ #include "MTUtils.hpp" #include "Zipper.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" + namespace Slic3r { enum SLAPrintStep : unsigned int { diff --git a/src/libslic3r/Semver.hpp b/src/libslic3r/Semver.hpp index 45d2bac1c4..a7489deb61 100644 --- a/src/libslic3r/Semver.hpp +++ b/src/libslic3r/Semver.hpp @@ -110,10 +110,30 @@ public: void set_maj(int maj) { ver.major = maj; } void set_min(int min) { ver.minor = min; } void set_patch(int patch) { ver.patch = patch; } - void set_metadata(boost::optional meta) { ver.metadata = meta ? strdup(*meta) : nullptr; } - void set_metadata(const char *meta) { ver.metadata = meta ? strdup(meta) : nullptr; } - void set_prerelease(boost::optional pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; } - void set_prerelease(const char *pre) { ver.prerelease = pre ? strdup(pre) : nullptr; } + void set_metadata(boost::optional meta) + { + if (ver.metadata) + free(ver.metadata); + ver.metadata = meta ? strdup(*meta) : nullptr; + } + void set_metadata(const char *meta) + { + if (ver.metadata) + free(ver.metadata); + ver.metadata = meta ? strdup(meta) : nullptr; + } + void set_prerelease(boost::optional pre) + { + if (ver.prerelease) + free(ver.prerelease); + ver.prerelease = pre ? strdup(*pre) : nullptr; + } + void set_prerelease(const char *pre) + { + if (ver.prerelease) + free(ver.prerelease); + ver.prerelease = pre ? strdup(pre) : nullptr; + } // Comparison bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 3f9dac82af..52c9e10d3c 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -26,6 +26,8 @@ #define ENABLE_GCODE_VIEWER_DATA_CHECKING 0 // Enable project dirty state manager debug window #define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0 +// Disable using instanced models to render options in gcode preview +#define DISABLE_GCODEVIEWER_INSTANCED_MODELS 1 // Enable rendering of objects using environment map @@ -47,4 +49,27 @@ #define ENABLE_Z_OFFSET_CORRECTION (1 && ENABLE_2_4_1_RC) +//==================== +// 2.5.0.alpha1 techs +//==================== +#define ENABLE_2_5_0_ALPHA1 1 + +// Enable changes in preview layout +#define ENABLE_PREVIEW_LAYOUT (1 && ENABLE_2_5_0_ALPHA1) +// Enable drawing the items in legend toolbar using icons +#define ENABLE_LEGEND_TOOLBAR_ICONS (1 && ENABLE_PREVIEW_LAYOUT) +// Enable coloring of toolpaths in preview by layer time +#define ENABLE_PREVIEW_LAYER_TIME (1 && ENABLE_2_5_0_ALPHA1) +// Enable showing time estimate for travel moves in legend +#define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1) +// Enable not killing focus in object manipulator fields when hovering over 3D scene +#define ENABLE_OBJECT_MANIPULATOR_FOCUS (1 && ENABLE_2_5_0_ALPHA1) +// Enable removal of wipe tower magic object_id equal to 1000 +#define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) +// Enable removal of old OpenGL render calls +#define ENABLE_GLBEGIN_GLEND_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) +// Enable show non-manifold edges +#define ENABLE_SHOW_NON_MANIFOLD_EDGES (1 && ENABLE_2_5_0_ALPHA1) + + #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 93a09a0d98..0dffdaab0e 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1239,13 +1239,30 @@ bool its_is_splittable(const indexed_triangle_set &its, const std::vector size_t its_num_open_edges(const std::vector &face_neighbors) { size_t num_open_edges = 0; - for (Vec3i neighbors : face_neighbors) + for (const Vec3i& neighbors : face_neighbors) for (int n : neighbors) if (n < 0) ++ num_open_edges; return num_open_edges; } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +std::vector> its_get_open_edges(const indexed_triangle_set& its) +{ + std::vector> ret; + std::vector face_neighbors = its_face_neighbors(its); + for (size_t i = 0; i < face_neighbors.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + if (face_neighbors[i][j] < 0) { + const Vec2i edge_indices = its_triangle_edge(its.indices[i], j); + ret.emplace_back(edge_indices[0], edge_indices[1]); + } + } + } + return ret; +} +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + size_t its_num_open_edges(const indexed_triangle_set &its) { return its_num_open_edges(its_face_neighbors(its)); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 3a0b2c2cec..3f3af0261c 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -227,6 +227,12 @@ bool its_is_splittable(const indexed_triangle_set &its, const std::vector size_t its_num_open_edges(const indexed_triangle_set &its); size_t its_num_open_edges(const std::vector &face_neighbors); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +// Calculate and returns the list of unconnected face edges. +// Each edge is represented by the indices of the two endpoint vertices +std::vector> its_get_open_edges(const indexed_triangle_set& its); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + // Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors. void its_shrink_to_fit(indexed_triangle_set &its); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 1de3a5ef86..10790ef491 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -361,7 +362,7 @@ inline std::string get_time_dhms(float time_in_secs) else if (minutes > 0) ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs); else - ::sprintf(buffer, "%ds", (int)time_in_secs); + ::sprintf(buffer, "%ds", (int)std::round(time_in_secs)); return buffer; } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 34cc980513..34c0efd014 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -169,9 +169,11 @@ set(SLIC3R_GUI_SOURCES GUI/PrintHostDialogs.cpp GUI/PrintHostDialogs.hpp GUI/Jobs/Job.hpp - GUI/Jobs/Job.cpp - GUI/Jobs/PlaterJob.hpp - GUI/Jobs/PlaterJob.cpp + GUI/Jobs/Worker.hpp + GUI/Jobs/BoostThreadWorker.hpp + GUI/Jobs/BoostThreadWorker.cpp + GUI/Jobs/BusyCursorJob.hpp + GUI/Jobs/PlaterWorker.hpp GUI/Jobs/ArrangeJob.hpp GUI/Jobs/ArrangeJob.cpp GUI/Jobs/RotoptimizeJob.hpp @@ -183,6 +185,8 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/ProgressIndicator.hpp GUI/Jobs/NotificationProgressIndicator.hpp GUI/Jobs/NotificationProgressIndicator.cpp + GUI/Jobs/ThreadSafeQueue.hpp + GUI/Jobs/SLAImportDialog.hpp GUI/ProgressStatusBar.hpp GUI/ProgressStatusBar.cpp GUI/Mouse3DController.cpp @@ -258,6 +262,12 @@ endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) +foreach(_source IN ITEMS ${SLIC3R_GUI_SOURCES}) + get_filename_component(_source_path "${_source}" PATH) + string(REPLACE "/" "\\" _group_path "${_source_path}") + source_group("${_group_path}" FILES "${_source}") +endforeach() + encoding_check(libslic3r_gui) target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES}) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index bd5ca9f521..c4f1a4407c 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -19,8 +19,10 @@ #include static const float GROUND_Z = -0.02f; -static const std::array DEFAULT_MODEL_COLOR = { 0.235f, 0.235f, 0.235f, 1.0f }; -static const std::array PICKING_MODEL_COLOR = { 0.0f, 0.0f, 0.0f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_MODEL_COLOR = Slic3r::ColorRGBA::DARK_GRAY(); +static const Slic3r::ColorRGBA PICKING_MODEL_COLOR = Slic3r::ColorRGBA::BLACK(); +static const Slic3r::ColorRGBA DEFAULT_SOLID_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_TRANSPARENT_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 0.6f }; namespace Slic3r { namespace GUI { @@ -99,7 +101,7 @@ const float Bed3D::Axes::DefaultStemLength = 25.0f; const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; const float Bed3D::Axes::DefaultTipLength = 5.0f; -void Bed3D::Axes::render() const +void Bed3D::Axes::render() { auto render_axis = [this](const Transform3f& transform) { glsafe(::glPushMatrix()); @@ -109,7 +111,7 @@ void Bed3D::Axes::render() const }; if (!m_arrow.is_initialized()) - const_cast(&m_arrow)->init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); + m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) @@ -121,15 +123,27 @@ void Bed3D::Axes::render() const shader->set_uniform("emission_factor", 0.0f); // x axis - const_cast(&m_arrow)->set_color(-1, { 0.75f, 0.0f, 0.0f, 1.0f }); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_arrow.set_color(ColorRGBA::X()); +#else + m_arrow.set_color(-1, ColorRGBA::X()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); // y axis - const_cast(&m_arrow)->set_color(-1, { 0.0f, 0.75f, 0.0f, 1.0f }); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_arrow.set_color(ColorRGBA::Y()); +#else + m_arrow.set_color(-1, ColorRGBA::Y()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); // z axis - const_cast(&m_arrow)->set_color(-1, { 0.0f, 0.0f, 0.75f, 1.0f }); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_arrow.set_color(ColorRGBA::Z()); +#else + m_arrow.set_color(-1, ColorRGBA::Z()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL render_axis(Geometry::assemble_transform(m_origin).cast()); shader->stop_using(); @@ -228,15 +242,18 @@ void Bed3D::render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_fact void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture, bool picking) { - float* factor = const_cast(&m_scale_factor); - *factor = scale_factor; + m_scale_factor = scale_factor; if (show_axes) render_axes(); glsafe(::glEnable(GL_DEPTH_TEST)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_model.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR); +#else m_model.set_color(-1, picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL switch (m_type) { @@ -328,13 +345,13 @@ std::tuple Bed3D::detect_type(const Point return { Type::Custom, {}, {} }; } -void Bed3D::render_axes() const +void Bed3D::render_axes() { if (m_build_volume.valid()) m_axes.render(); } -void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) const +void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) { if (!bottom) render_model(); @@ -343,26 +360,23 @@ void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) co render_texture(bottom, canvas); } -void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const +void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) { - GLTexture* texture = const_cast(&m_texture); - GLTexture* temp_texture = const_cast(&m_temp_texture); - if (m_texture_filename.empty()) { - texture->reset(); + m_texture.reset(); render_default(bottom, false); return; } - if (texture->get_id() == 0 || texture->get_source() != m_texture_filename) { - texture->reset(); + if (m_texture.get_id() == 0 || m_texture.get_source() != m_texture_filename) { + m_texture.reset(); if (boost::algorithm::iends_with(m_texture_filename, ".svg")) { // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) { + if (m_temp_texture.get_id() == 0 || m_temp_texture.get_source() != m_texture_filename) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) { + if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) { render_default(bottom, false); return; } @@ -370,15 +384,15 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } // starts generating the main texture, compression will run asynchronously - if (!texture->load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) { + if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) { render_default(bottom, false); return; } } else if (boost::algorithm::iends_with(m_texture_filename, ".png")) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) { - if (!temp_texture->load_from_file(m_texture_filename, false, GLTexture::None, false)) { + if (m_temp_texture.get_id() == 0 || m_temp_texture.get_source() != m_texture_filename) { + if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) { render_default(bottom, false); return; } @@ -386,7 +400,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } // starts generating the main texture, compression will run asynchronously - if (!texture->load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) { + if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) { render_default(bottom, false); return; } @@ -396,13 +410,13 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const return; } } - else if (texture->unsent_compressed_data_available()) { + else if (m_texture.unsent_compressed_data_available()) { // sends to gpu the already available compressed levels of the main texture - texture->send_compressed_data_to_gpu(); + m_texture.send_compressed_data_to_gpu(); // the temporary texture is not needed anymore, reset it - if (temp_texture->get_id() != 0) - temp_texture->reset(); + if (m_temp_texture.get_id() != 0) + m_temp_texture.reset(); canvas.request_extra_frame(); } @@ -414,11 +428,9 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const shader->set_uniform("transparent_background", bottom); shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); - unsigned int* vbo_id = const_cast(&m_vbo_id); - - if (*vbo_id == 0) { - glsafe(::glGenBuffers(1, vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id)); + if (m_vbo_id == 0) { + glsafe(::glGenBuffers(1, &m_vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } @@ -439,12 +451,12 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); // show the temporary texture while no compressed data is available - GLuint tex_id = (GLuint)temp_texture->get_id(); + GLuint tex_id = (GLuint)m_temp_texture.get_id(); if (tex_id == 0) - tex_id = (GLuint)texture->get_id(); + tex_id = (GLuint)m_texture.get_id(); glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); if (position_id != -1) { glsafe(::glEnableVertexAttribArray(position_id)); @@ -478,38 +490,40 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } } -void Bed3D::render_model() const +void Bed3D::render_model() { if (m_model_filename.empty()) return; - GLModel* model = const_cast(&m_model); - - if (model->get_filename() != m_model_filename && model->init_from_file(m_model_filename)) { - model->set_color(-1, DEFAULT_MODEL_COLOR); + if (m_model.get_filename() != m_model_filename && m_model.init_from_file(m_model_filename)) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_model.set_color(DEFAULT_MODEL_COLOR); +#else + m_model.set_color(-1, DEFAULT_MODEL_COLOR); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad - *const_cast(&m_model_offset) = to_3d(m_build_volume.bounding_volume2d().center(), -0.03); + m_model_offset = to_3d(m_build_volume.bounding_volume2d().center(), -0.03); // update extended bounding box - const_cast(m_extended_bounding_box) = this->calc_extended_bounding_box(); + m_extended_bounding_box = this->calc_extended_bounding_box(); } - if (!model->get_filename().empty()) { + if (!m_model.get_filename().empty()) { GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.0f); glsafe(::glPushMatrix()); glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z())); - model->render(); + m_model.render(); glsafe(::glPopMatrix()); shader->stop_using(); } } } -void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture, bool picking) const +void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture, bool picking) { if (m_texture_filename.empty() && m_model_filename.empty()) { render_default(bottom, picking); @@ -523,13 +537,13 @@ void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture, bo render_texture(bottom, canvas); } -void Bed3D::render_default(bool bottom, bool picking) const +void Bed3D::render_default(bool bottom, bool picking) { - const_cast(&m_texture)->reset(); + m_texture.reset(); - unsigned int triangles_vcount = m_triangles.get_vertices_count(); + const unsigned int triangles_vcount = m_triangles.get_vertices_count(); if (triangles_vcount > 0) { - bool has_model = !m_model.get_filename().empty(); + const bool has_model = !m_model.get_filename().empty(); glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); @@ -550,10 +564,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/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 639dc6c163..82c6b817be 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -60,7 +60,7 @@ class Bed3D m_arrow.reset(); } float get_total_length() const { return m_stem_length + DefaultTipLength; } - void render() const; + void render(); }; public: @@ -130,12 +130,12 @@ private: static std::tuple detect_type(const Pointfs& shape); void render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture, bool picking); - void render_axes() const; - void render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) const; - void render_texture(bool bottom, GLCanvas3D& canvas) const; - void render_model() const; - void render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture, bool picking) const; - void render_default(bool bottom, bool picking) const; + void render_axes(); + void render_system(GLCanvas3D& canvas, bool bottom, bool show_texture); + void render_texture(bool bottom, GLCanvas3D& canvas); + void render_model(); + void render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture, bool picking); + void render_default(bool bottom, bool picking); void release_VBOs(); }; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 263ba0e73a..4e929d060c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -302,10 +302,10 @@ void GLVolume::SinkingContours::render() void GLVolume::SinkingContours::update() { - int object_idx = m_parent.object_idx(); - Model& model = GUI::wxGetApp().plater()->model(); + const int object_idx = m_parent.object_idx(); + const Model& model = GUI::wxGetApp().plater()->model(); - if (0 <= object_idx && object_idx < (int)model.objects.size() && m_parent.is_sinking() && !m_parent.is_below_printbed()) { + if (0 <= object_idx && object_idx < int(model.objects.size()) && m_parent.is_sinking() && !m_parent.is_below_printbed()) { const BoundingBoxf3& box = m_parent.transformed_convex_hull_bounding_box(); if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) { m_old_box = box; @@ -314,16 +314,37 @@ void GLVolume::SinkingContours::update() const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh(); m_model.reset(); - GUI::GLModel::InitializationData init_data; + GUI::GLModel::Geometry init_data; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3, GUI::GLModel::Geometry::EIndexType::UINT }; + init_data.color = ColorRGBA::WHITE(); + unsigned int vertices_counter = 0; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL MeshSlicingParams slicing_params; slicing_params.trafo = m_parent.world_matrix(); - Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); - for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Triangles; + const Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); + for (const ExPolygon& expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL const std::vector triangulation = triangulate_expolygon_3d(expoly); + init_data.vertices.reserve(init_data.vertices.size() + triangulation.size() * GUI::GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(init_data.indices.size() + triangulation.size() * GUI::GLModel::Geometry::index_stride_bytes(init_data.format)); for (const Vec3d& v : triangulation) { - entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.015f)); // add a small positive z to avoid z-fighting + init_data.add_vertex((Vec3f)(v.cast() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + init_data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + } + m_model.init_from(std::move(init_data)); +#else + GUI::GLModel::Geometry::Entity entity; + entity.type = GUI::GLModel::EPrimitiveType::Triangles; + const std::vector triangulation = triangulate_expolygon_3d(expoly); + entity.positions.reserve(entity.positions.size() + triangulation.size()); + entity.normals.reserve(entity.normals.size() + triangulation.size()); + entity.indices.reserve(entity.indices.size() + triangulation.size() / 3); + for (const Vec3d& v : triangulation) { + entity.positions.emplace_back(v.cast() + 0.015f * Vec3f::UnitZ()); // add a small positive z to avoid z-fighting entity.normals.emplace_back(Vec3f::UnitZ()); const size_t positions_count = entity.positions.size(); if (positions_count % 3 == 0) { @@ -334,8 +355,8 @@ void GLVolume::SinkingContours::update() } init_data.entities.emplace_back(entity); } - m_model.init_from(init_data); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } else m_shift = box.center() - m_old_box.center(); @@ -344,25 +365,103 @@ void GLVolume::SinkingContours::update() m_model.reset(); } -const std::array GLVolume::SELECTED_COLOR = { 0.0f, 1.0f, 0.0f, 1.0f }; -const std::array GLVolume::HOVER_SELECT_COLOR = { 0.4f, 0.9f, 0.1f, 1.0f }; -const std::array GLVolume::HOVER_DESELECT_COLOR = { 1.0f, 0.75f, 0.75f, 1.0f }; -const std::array GLVolume::OUTSIDE_COLOR = { 0.0f, 0.38f, 0.8f, 1.0f }; -const std::array GLVolume::SELECTED_OUTSIDE_COLOR = { 0.19f, 0.58f, 1.0f, 1.0f }; -const std::array GLVolume::DISABLED_COLOR = { 0.25f, 0.25f, 0.25f, 1.0f }; -const std::array GLVolume::SLA_SUPPORT_COLOR = { 0.75f, 0.75f, 0.75f, 1.0f }; -const std::array GLVolume::SLA_PAD_COLOR = { 0.0f, 0.2f, 0.0f, 1.0f }; -const std::array GLVolume::NEUTRAL_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f }; -const std::array, 4> GLVolume::MODEL_COLOR = { { - { 1.0f, 1.0f, 0.0f, 1.f }, - { 1.0f, 0.5f, 0.5f, 1.f }, - { 0.5f, 1.0f, 0.5f, 1.f }, - { 0.5f, 0.5f, 1.0f, 1.f } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +void GLVolume::NonManifoldEdges::render() +{ + update(); + + glsafe(::glLineWidth(2.0f)); + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(m_parent.world_matrix().data())); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_model.set_color(complementary(m_parent.render_color)); +#else + m_model.set_color(-1, complementary(m_parent.render_color)); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + m_model.render(); + glsafe(::glPopMatrix()); +} + +void GLVolume::NonManifoldEdges::update() +{ + if (!m_update_needed) + return; + + m_model.reset(); + const int object_idx = m_parent.object_idx(); + const Model& model = GUI::wxGetApp().plater()->model(); + if (0 <= object_idx && object_idx < int(model.objects.size())) { + const ModelObject* model_object = model.objects[object_idx]; + const int volume_idx = m_parent.volume_idx(); + if (0 <= volume_idx && volume_idx < int(model_object->volumes.size())) { + const ModelVolume* model_volume = model_object->volumes[volume_idx]; + const TriangleMesh& mesh = model_volume->mesh(); + const std::vector> edges = its_get_open_edges(mesh.its); + if (!edges.empty()) { + GUI::GLModel::Geometry init_data; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Lines, GUI::GLModel::Geometry::EVertexLayout::P3, GUI::GLModel::Geometry::EIndexType::UINT }; + init_data.vertices.reserve(2 * edges.size() * GUI::GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * edges.size() * GUI::GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + indices + unsigned int vertices_count = 0; + for (const std::pair& edge : edges) { + init_data.add_vertex((Vec3f)mesh.its.vertices[edge.first].cast()); + init_data.add_vertex((Vec3f)mesh.its.vertices[edge.second].cast()); + vertices_count += 2; + init_data.add_uint_line(vertices_count - 2, vertices_count - 1); + } + m_model.init_from(std::move(init_data)); +#else + GUI::GLModel::Geometry::Entity entity; + entity.type = GUI::GLModel::EPrimitiveType::Lines; + + entity.positions.reserve(2 * edges.size()); + entity.normals.reserve(2 * edges.size()); + entity.indices.reserve(2 * edges.size()); + for (const std::pair& edge : edges) { + entity.positions.emplace_back(mesh.its.vertices[edge.first].cast()); + entity.positions.emplace_back(mesh.its.vertices[edge.second].cast()); + entity.normals.emplace_back(Vec3f::UnitZ()); + entity.normals.emplace_back(Vec3f::UnitZ()); + entity.indices.emplace_back(entity.positions.size() - 2); + entity.indices.emplace_back(entity.positions.size() - 1); + } + + init_data.entities.emplace_back(entity); + m_model.init_from(init_data); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + } + } + } + + m_update_needed = false; +} +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + +const ColorRGBA GLVolume::SELECTED_COLOR = ColorRGBA::GREEN(); +const ColorRGBA GLVolume::HOVER_SELECT_COLOR = { 0.4f, 0.9f, 0.1f, 1.0f }; +const ColorRGBA GLVolume::HOVER_DESELECT_COLOR = { 1.0f, 0.75f, 0.75f, 1.0f }; +const ColorRGBA GLVolume::OUTSIDE_COLOR = { 0.0f, 0.38f, 0.8f, 1.0f }; +const ColorRGBA GLVolume::SELECTED_OUTSIDE_COLOR = { 0.19f, 0.58f, 1.0f, 1.0f }; +const ColorRGBA GLVolume::DISABLED_COLOR = ColorRGBA::DARK_GRAY(); +const ColorRGBA GLVolume::SLA_SUPPORT_COLOR = ColorRGBA::LIGHT_GRAY(); +const ColorRGBA GLVolume::SLA_PAD_COLOR = { 0.0f, 0.2f, 0.0f, 1.0f }; +const ColorRGBA GLVolume::NEUTRAL_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f }; +const std::array GLVolume::MODEL_COLOR = { { + ColorRGBA::YELLOW(), + { 1.0f, 0.5f, 0.5f, 1.0f }, + { 0.5f, 1.0f, 0.5f, 1.0f }, + { 0.5f, 0.5f, 1.0f, 1.0f } } }; GLVolume::GLVolume(float r, float g, float b, float a) : m_sla_shift_z(0.0) , m_sinking_contours(*this) +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + , m_non_manifold_edges(*this) +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES // geometry_id == 0 -> invalid , geometry_id(std::pair(0, 0)) , extruder_id(0) @@ -377,7 +476,6 @@ GLVolume::GLVolume(float r, float g, float b, float a) , is_modifier(false) , is_wipe_tower(false) , is_extrusion_path(false) - , force_transparent(false) , force_native_color(false) , force_neutral_color(false) , force_sinking_contours(false) @@ -388,22 +486,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) set_render_color(color); } -void GLVolume::set_color(const std::array& rgba) -{ - color = rgba; -} - -void GLVolume::set_render_color(float r, float g, float b, float a) -{ - render_color = { r, g, b, a }; -} - -void GLVolume::set_render_color(const std::array& rgba) -{ - render_color = rgba; -} - -void GLVolume::set_render_color() +void GLVolume::set_render_color(bool force_transparent) { bool outside = is_outside || is_below_printbed(); @@ -432,40 +515,28 @@ void GLVolume::set_render_color() set_render_color(color); } - if (!printable) { - render_color[0] /= 4; - render_color[1] /= 4; - render_color[2] /= 4; - } + if (!printable) + render_color = saturate(render_color, 0.25f); if (force_transparent) - render_color[3] = color[3]; + render_color.a(color.a()); } -std::array color_from_model_volume(const ModelVolume& model_volume) +ColorRGBA color_from_model_volume(const ModelVolume& model_volume) { - std::array color; - if (model_volume.is_negative_volume()) { - color[0] = 0.2f; - color[1] = 0.2f; - color[2] = 0.2f; - } - else if (model_volume.is_modifier()) { - color[0] = 1.0f; - color[1] = 1.0f; - color[2] = 0.2f; - } - else if (model_volume.is_support_blocker()) { - color[0] = 1.0f; - color[1] = 0.2f; - color[2] = 0.2f; - } - else if (model_volume.is_support_enforcer()) { - color[0] = 0.2f; - color[1] = 0.2f; - color[2] = 1.0f; - } - color[3] = model_volume.is_model_part() ? 1.f : 0.5f; + ColorRGBA color; + if (model_volume.is_negative_volume()) + color = { 0.2f, 0.2f, 0.2f, 1.0f }; + else if (model_volume.is_modifier()) + color = { 1.0, 1.0f, 0.2f, 1.0f }; + else if (model_volume.is_support_blocker()) + color = { 1.0f, 0.2f, 0.2f, 1.0f }; + else if (model_volume.is_support_enforcer()) + color = { 0.2f, 0.2f, 1.0f, 1.0f }; + + if (!model_volume.is_model_part()) + color.a(0.5f); + return color; } @@ -599,6 +670,13 @@ void GLVolume::render_sinking_contours() m_sinking_contours.render(); } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +void GLVolume::render_non_manifold_edges() +{ + m_non_manifold_edges.render(); +} +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, @@ -625,8 +703,8 @@ int GLVolumeCollection::load_object_volume( const int extruder_id = model_volume->extruder_id(); const ModelInstance *instance = model_object->instances[instance_idx]; const TriangleMesh &mesh = model_volume->mesh(); - std::array color = GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4]; - color[3] = model_volume->is_model_part() ? 1.f : 0.5f; + ColorRGBA color = GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4]; + color.a(model_volume->is_model_part() ? 1.0f : 0.5f); this->volumes.emplace_back(new GLVolume(color)); GLVolume& v = *this->volumes.back(); v.set_color(color_from_model_volume(*model_volume)); @@ -697,9 +775,15 @@ void GLVolumeCollection::load_object_auxiliary( } } +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +int GLVolumeCollection::load_wipe_tower_preview( + float pos_x, float pos_y, float width, float depth, float height, + float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized) +#else int GLVolumeCollection::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) +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL { if (depth < 0.01f) return int(this->volumes.size() - 1); @@ -707,13 +791,13 @@ int GLVolumeCollection::load_wipe_tower_preview( height = 0.1f; TriangleMesh mesh; - std::array color = { 0.5f, 0.5f, 0.0f, 1.0f }; + ColorRGBA color = ColorRGBA::DARK_YELLOW(); // In case we don't know precise dimensions of the wipe tower yet, we'll draw // the box with different color with one side jagged: if (size_unknown) { - color[0] = 0.9f; - color[1] = 0.6f; + color.r(0.9f); + color.g(0.6f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway. depth = std::max(depth, 10.f); @@ -761,7 +845,11 @@ int GLVolumeCollection::load_wipe_tower_preview( v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + v.composite_id = GLVolume::CompositeID(INT_MAX, 0, 0); +#else v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL v.geometry_id.first = 0; v.geometry_id.second = wipe_tower_instance_id().id; v.is_wipe_tower = true; @@ -769,14 +857,14 @@ int GLVolumeCollection::load_wipe_tower_preview( return int(volumes.size() - 1); } -GLVolume* GLVolumeCollection::new_toolpath_volume(const std::array& rgba, size_t reserve_vbo_floats) +GLVolume* GLVolumeCollection::new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats) { GLVolume *out = new_nontoolpath_volume(rgba, reserve_vbo_floats); out->is_extrusion_path = true; return out; } -GLVolume* GLVolumeCollection::new_nontoolpath_volume(const std::array& rgba, size_t reserve_vbo_floats) +GLVolume* GLVolumeCollection::new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats) { GLVolume *out = new GLVolume(rgba); out->is_extrusion_path = false; @@ -793,7 +881,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) && @@ -829,6 +917,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab if (shader == nullptr) return; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* sink_shader = GUI::wxGetApp().get_shader("flat"); + GLShaderProgram* edges_shader = GUI::wxGetApp().get_shader("flat"); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + if (type == ERenderType::Transparent) { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -839,20 +932,31 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_CULL_FACE)); for (GLVolumeWithIdAndZ& volume : to_render) { - if (type == ERenderType::Transparent) - volume.first->force_transparent = true; - volume.first->set_render_color(); - if (type == ERenderType::Transparent) - volume.first->force_transparent = false; + volume.first->set_render_color(true); // render sinking contours of non-hovered volumes - if (m_show_sinking_contours) - if (volume.first->is_sinking() && !volume.first->is_below_printbed() && - volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) { - shader->stop_using(); - volume.first->render_sinking_contours(); - shader->start_using(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + shader->stop_using(); + if (sink_shader != nullptr) { + sink_shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + if (m_show_sinking_contours) { + if (volume.first->is_sinking() && !volume.first->is_below_printbed() && + volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) { +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + shader->stop_using(); +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL + volume.first->render_sinking_contours(); +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + shader->start_using(); +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL + } } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + sink_shader->stop_using(); + } + shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); @@ -892,18 +996,50 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab } if (m_show_sinking_contours) { - for (GLVolumeWithIdAndZ& volume : to_render) { - // render sinking contours of hovered/displaced volumes - if (volume.first->is_sinking() && !volume.first->is_below_printbed() && - (volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) { - shader->stop_using(); - glsafe(::glDepthFunc(GL_ALWAYS)); - volume.first->render_sinking_contours(); - glsafe(::glDepthFunc(GL_LESS)); - shader->start_using(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + shader->stop_using(); + if (sink_shader != nullptr) { + sink_shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + for (GLVolumeWithIdAndZ& volume : to_render) { + // render sinking contours of hovered/displaced volumes + if (volume.first->is_sinking() && !volume.first->is_below_printbed() && + (volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) { +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + shader->stop_using(); +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL + glsafe(::glDepthFunc(GL_ALWAYS)); + volume.first->render_sinking_contours(); + glsafe(::glDepthFunc(GL_LESS)); +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + shader->start_using(); +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL + } + } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + sink_shader->start_using(); + } + shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + } + +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +#if ENABLE_GLBEGIN_GLEND_REMOVAL + shader->stop_using(); + if (edges_shader != nullptr) { + edges_shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + if (m_show_non_manifold_edges && GUI::wxGetApp().app_config->get("non_manifold_edges") == "1") { + for (GLVolumeWithIdAndZ& volume : to_render) { + volume.first->render_non_manifold_edges(); } } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + edges_shader->stop_using(); } + shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES if (disable_cullface) glsafe(::glEnable(GL_CULL_FACE)); @@ -972,8 +1108,7 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo void GLVolumeCollection::reset_outside_state() { - for (GLVolume* volume : this->volumes) - { + for (GLVolume* volume : this->volumes) { if (volume != nullptr) volume->is_outside = false; } @@ -981,46 +1116,18 @@ void GLVolumeCollection::reset_outside_state() void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* config) { - static const float inv_255 = 1.0f / 255.0f; + using ColorItem = std::pair; + std::vector colors; - struct Color - { - std::string text; - unsigned char rgb[3]; - - Color() - : text("") - { - rgb[0] = 255; - rgb[1] = 255; - rgb[2] = 255; - } - - void set(const std::string& text, unsigned char* rgb) - { - this->text = text; - ::memcpy((void*)this->rgb, (const void*)rgb, 3 * sizeof(unsigned char)); - } - }; - - if (config == nullptr) - return; - - unsigned char rgb[3]; - std::vector colors; - - if (static_cast(config->opt_int("printer_technology")) == ptSLA) - { + if (static_cast(config->opt_int("printer_technology")) == ptSLA) { const std::string& txt_color = config->opt_string("material_colour").empty() ? print_config_def.get("material_colour")->get_default_value()->value : config->opt_string("material_colour"); - if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) { - colors.resize(1); - colors[0].set(txt_color, rgb); - } + ColorRGB rgb; + if (decode_color(txt_color, rgb)) + colors.push_back({ txt_color, rgb }); } - else - { + else { const ConfigOptionStrings* extruders_opt = dynamic_cast(config->option("extruder_colour")); if (extruders_opt == nullptr) return; @@ -1029,37 +1136,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); + ColorRGB rgb; + if (decode_color(ext_color, rgb)) + colors[i] = { ext_color, rgb }; 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, rgb)) + colors[i] = { fil_color, rgb }; } } } 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 = to_rgba(color.second, volume->color.a()); } } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 6d82e3bb7c..5aaa759f2f 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -7,6 +7,7 @@ #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/Color.hpp" #include "GLModel.hpp" @@ -43,7 +44,7 @@ class ModelVolume; enum ModelInstanceEPrintVolumeState : unsigned char; // Return appropriate color based on the ModelVolume. -std::array color_from_model_volume(const ModelVolume& model_volume); +extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume); // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. @@ -248,16 +249,16 @@ private: class GLVolume { public: - static const std::array SELECTED_COLOR; - static const std::array HOVER_SELECT_COLOR; - static const std::array HOVER_DESELECT_COLOR; - static const std::array OUTSIDE_COLOR; - static const std::array SELECTED_OUTSIDE_COLOR; - static const std::array DISABLED_COLOR; - static const std::array SLA_SUPPORT_COLOR; - static const std::array SLA_PAD_COLOR; - static const std::array NEUTRAL_COLOR; - static const std::array, 4> MODEL_COLOR; + static const ColorRGBA SELECTED_COLOR; + static const ColorRGBA HOVER_SELECT_COLOR; + static const ColorRGBA HOVER_DESELECT_COLOR; + static const ColorRGBA OUTSIDE_COLOR; + static const ColorRGBA SELECTED_OUTSIDE_COLOR; + static const ColorRGBA DISABLED_COLOR; + static const ColorRGBA SLA_SUPPORT_COLOR; + static const ColorRGBA SLA_PAD_COLOR; + static const ColorRGBA NEUTRAL_COLOR; + static const std::array MODEL_COLOR; enum EHoverState : unsigned char { @@ -267,8 +268,8 @@ public: HS_Deselect }; - GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); - GLVolume(const std::array& rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} + GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f); + GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {} private: Geometry::Transformation m_instance_transformation; @@ -303,11 +304,30 @@ private: SinkingContours m_sinking_contours; +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + class NonManifoldEdges + { + GLVolume& m_parent; + GUI::GLModel m_model; + bool m_update_needed{ true }; + + public: + NonManifoldEdges(GLVolume& volume) : m_parent(volume) {} + void render(); + void set_as_dirty() { m_update_needed = true; } + + private: + void update(); + }; + + NonManifoldEdges m_non_manifold_edges; +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + public: // Color of the triangles / quads held by this volume. - std::array color; + ColorRGBA color; // Color used to render this volume. - std::array render_color; + ColorRGBA render_color; struct CompositeID { CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {} @@ -357,9 +377,7 @@ public: bool is_wipe_tower : 1; // Wheter or not this volume has been generated from an extrusion path bool is_extrusion_path : 1; - // Wheter or not to always render this volume using its own alpha - bool force_transparent : 1; - // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) + // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) bool force_native_color : 1; // Whether or not render this volume in neutral bool force_neutral_color : 1; @@ -393,11 +411,10 @@ public: return out; } - void set_color(const std::array& rgba); - void set_render_color(float r, float g, float b, float a); - void set_render_color(const std::array& rgba); + void set_color(const ColorRGBA& rgba) { color = rgba; } + void set_render_color(const ColorRGBA& rgba) { render_color = rgba; } // Sets render color in dependence of current state - void set_render_color(); + void set_render_color(bool force_transparent); // set color according to model volume void set_color_from_model_volume(const ModelVolume& model_volume); @@ -462,8 +479,8 @@ public: void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared(convex_hull); } void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } - int object_idx() const { return this->composite_id.object_id; } - int volume_idx() const { return this->composite_id.volume_id; } + int object_idx() const { return this->composite_id.object_id; } + int volume_idx() const { return this->composite_id.volume_id; } int instance_idx() const { return this->composite_id.instance_id; } Transform3d world_matrix() const; @@ -502,6 +519,9 @@ public: bool is_sinking() const; bool is_below_printbed() const; void render_sinking_contours(); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + void render_non_manifold_edges(); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES // Return an estimate of the memory consumed by this class. size_t cpu_memory_used() const { @@ -558,7 +578,10 @@ private: }; Slope m_slope; - bool m_show_sinking_contours = false; + bool m_show_sinking_contours{ false }; +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + bool m_show_non_manifold_edges{ true }; +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES public: GLVolumePtrs volumes; @@ -592,11 +615,16 @@ public: size_t timestamp, bool opengl_initialized); +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int load_wipe_tower_preview( + float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); +#else 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); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - GLVolume* new_toolpath_volume(const std::array& rgba, size_t reserve_vbo_floats = 0); - GLVolume* new_nontoolpath_volume(const std::array& rgba, size_t reserve_vbo_floats = 0); + GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); + GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); // Render the volumes by OpenGL. void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; @@ -626,6 +654,9 @@ public: void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; } void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); } void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; } +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 2b21b9ee01..e444fb03c4 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -2,6 +2,7 @@ #include "I18N.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" @@ -133,12 +134,12 @@ wxString CopyrightsDialog::get_html_text() wxColour bgr_clr = wxGetApp().get_window_default_clr();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); const auto text_clr = wxGetApp().get_label_clr_default(); - const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); - const wxString copyright_str = _(L("Copyright")) + "© "; + const wxString copyright_str = _L("Copyright") + "© "; // TRN "Slic3r _is licensed under the_ License" - const wxString header_str = _(L("License agreements of all following programs (libraries) are part of application license agreement")); + const wxString header_str = _L("License agreements of all following programs (libraries) are part of application license agreement"); wxString text = wxString::Format( "" @@ -257,8 +258,8 @@ AboutDialog::AboutDialog() m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit())); wxFont font = get_default_font(this); const auto text_clr = wxGetApp().get_label_clr_default(); - auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); const int fs = font.GetPointSize()-1; int size[] = {fs,fs,fs,fs,fs,fs,fs}; diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 032aa28800..2f308a5073 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -18,6 +18,7 @@ namespace GUI { class ConfigOptionsGroup; using ConfigOptionsGroupShp = std::shared_ptr; +using ConfigOptionsGroupWkp = std::weak_ptr; struct BedShape { diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 39ba849d33..e23591fb62 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -395,21 +395,5 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); } -bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out) -{ - rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; - if (scolor.size() != 7 || scolor.front() != '#') - return false; - const char* c = scolor.data() + 1; - for (size_t i = 0; i < 3; ++i) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - return false; - rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); - } - return true; -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 4d1d383c41..5af90c5f7b 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -9,9 +9,12 @@ #include #endif +#include "libslic3r/Color.hpp" + struct NSVGimage; -namespace Slic3r { namespace GUI { +namespace Slic3r { +namespace GUI { class BitmapCache { @@ -43,11 +46,9 @@ public: wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = ""); wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false); - wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3], bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } + wxBitmap mksolid(size_t width, size_t height, const ColorRGB& rgb, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } - static bool parse_color(const std::string& scolor, unsigned char* rgb_out); - private: std::map m_map; double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs) diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index afa495d0c2..e2bea55d13 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -5,6 +5,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Time.hpp" +#include "libslic3r/Color.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" #include "wxExtensions.hpp" @@ -31,11 +32,9 @@ static wxString format_reason(const Config::Snapshot::Reason reason) static std::string get_color(wxColour colour) { - wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - return clr_str.ToStdString(); + return encode_color(ColorRGB(colour.Red(), colour.Green(), colour.Blue())); }; - static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active, bool dark_mode) { // Start by declaring a row with an alternating background color. diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index dadf5d8ca3..4ff0898820 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -36,6 +36,7 @@ #include "libslic3r/Config.hpp" #include "libslic3r/libslic3r.h" #include "libslic3r/Model.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" @@ -746,9 +747,9 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector* are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); wxString text; if (all_printers) { @@ -2522,23 +2523,33 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese { wxString header, caption = _L("Configuration is edited in ConfigWizard"); const auto enabled_vendors = appconfig_new.vendors(); + const auto enabled_vendors_old = app_config->vendors(); bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); PrinterTechnology preferred_pt = ptAny; - auto get_preferred_printer_technology = [enabled_vendors, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { + auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { const auto config = enabled_vendors.find(bundle_name); PrinterTechnology pt = ptAny; if (config != enabled_vendors.end()) { for (const auto& model : bundle.vendor_profile->models) { if (const auto model_it = config->second.find(model.id); model_it != config->second.end() && model_it->second.size() > 0) { - if (pt == ptAny) - pt = model.technology; - // if preferred printer model has SLA printer technology it's important to check the model for multypart state - if (pt == ptSLA && suppress_sla_printer) - continue; - else + pt = model.technology; + const auto config_old = enabled_vendors_old.find(bundle_name); + if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; return pt; + } + + if (const auto model_it_old = config_old->second.find(model.id); + model_it_old == config_old->second.end() || model_it_old->second != model_it->second) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; + return pt; + } } } } @@ -2645,7 +2656,6 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese std::string preferred_model; std::string preferred_variant; - const auto enabled_vendors_old = app_config->vendors(); auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { const auto config = enabled_vendors.find(bundle_name); if (config == enabled_vendors.end()) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 3d914d5b66..ee5aacb191 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -2557,36 +2557,46 @@ bool Control::check_ticks_changed_event(Type type) std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder) { + auto opposite_one_color = [](const std::string& color) { + ColorRGB rgb; + decode_color(color, rgb); + return encode_color(opposite(rgb)); + }; + auto opposite_two_colors = [](const std::string& a, const std::string& b) { + ColorRGB rgb1; decode_color(a, rgb1); + ColorRGB rgb2; decode_color(b, rgb2); + return encode_color(opposite(rgb1, rgb2)); + }; + if (mode == SingleExtruder && type == ColorChange && m_use_default_colors) { #if 1 if (ticks.empty()) - return color_generator.get_opposite_color((*m_colors)[0]); - + return opposite_one_color((*m_colors)[0]); + auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick); - if (before_tick_it == ticks.end()) - { + if (before_tick_it == ticks.end()) { while (before_tick_it != ticks.begin()) if (--before_tick_it; before_tick_it->type == ColorChange) break; if (before_tick_it->type == ColorChange) - return color_generator.get_opposite_color(before_tick_it->color); - return color_generator.get_opposite_color((*m_colors)[0]); + return opposite_one_color(before_tick_it->color); + + return opposite_one_color((*m_colors)[0]); } - if (before_tick_it == ticks.begin()) - { + if (before_tick_it == ticks.begin()) { const std::string& frst_color = (*m_colors)[0]; if (before_tick_it->type == ColorChange) - return color_generator.get_opposite_color(frst_color, before_tick_it->color); + return opposite_two_colors(frst_color, before_tick_it->color); auto next_tick_it = before_tick_it; while (next_tick_it != ticks.end()) if (++next_tick_it; next_tick_it->type == ColorChange) break; if (next_tick_it->type == ColorChange) - return color_generator.get_opposite_color(frst_color, next_tick_it->color); + return opposite_two_colors(frst_color, next_tick_it->color); - return color_generator.get_opposite_color(frst_color); + return opposite_one_color(frst_color); } std::string frst_color = ""; @@ -2607,13 +2617,15 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int if (before_tick_it->type == ColorChange) { if (frst_color.empty()) - return color_generator.get_opposite_color(before_tick_it->color); - return color_generator.get_opposite_color(before_tick_it->color, frst_color); + return opposite_one_color(before_tick_it->color); + + return opposite_two_colors(before_tick_it->color, frst_color); } if (frst_color.empty()) - return color_generator.get_opposite_color((*m_colors)[0]); - return color_generator.get_opposite_color((*m_colors)[0], frst_color); + return opposite_one_color((*m_colors)[0]); + + return opposite_two_colors((*m_colors)[0], frst_color); #else const std::vector& colors = ColorPrintColors::get(); if (ticks.empty()) diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 23275cf2ad..e0f713d879 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -3,7 +3,6 @@ #include "libslic3r/CustomGCode.hpp" #include "wxExtensions.hpp" -#include "DoubleSlider_Utils.hpp" #include #include @@ -119,7 +118,6 @@ class TickCodeInfo // int m_default_color_idx = 0; std::vector* m_colors {nullptr}; - ColorGenerator color_generator; std::string get_color_for_tick(TickCode tick, Type type, const int extruder); diff --git a/src/slic3r/GUI/DoubleSlider_Utils.hpp b/src/slic3r/GUI/DoubleSlider_Utils.hpp index b5955f2fc4..283d527fa4 100644 --- a/src/slic3r/GUI/DoubleSlider_Utils.hpp +++ b/src/slic3r/GUI/DoubleSlider_Utils.hpp @@ -1,191 +1,8 @@ +#ifndef slic3r_GUI_DoubleSlider_Utils_hpp_ +#define slic3r_GUI_DoubleSlider_Utils_hpp_ + #include #include -#include "wx/colour.h" -class ColorGenerator -{ - // Some of next code is borrowed from https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both - typedef struct { - double r; // a fraction between 0 and 1 - double g; // a fraction between 0 and 1 - double b; // a fraction between 0 and 1 - } rgb; - - typedef struct { - double h; // angle in degrees - double s; // a fraction between 0 and 1 - double v; // a fraction between 0 and 1 - } hsv; - - //static hsv rgb2hsv(rgb in); - //static rgb hsv2rgb(hsv in); - - hsv rgb2hsv(rgb in) - { - hsv out; - double min, max, delta; - - min = in.r < in.g ? in.r : in.g; - min = min < in.b ? min : in.b; - - max = in.r > in.g ? in.r : in.g; - max = max > in.b ? max : in.b; - - out.v = max; // v - delta = max - min; - if (delta < 0.00001) - { - out.s = 0; - out.h = 0; // undefined, maybe nan? - return out; - } - if (max > 0.0) { // NOTE: if Max is == 0, this divide would cause a crash - out.s = (delta / max); // s - } - else { - // if max is 0, then r = g = b = 0 - // s = 0, h is undefined - out.s = 0.0; - out.h = NAN; // its now undefined - return out; - } - if (in.r >= max) // > is bogus, just keeps compilor happy - out.h = (in.g - in.b) / delta; // between yellow & magenta - else - if (in.g >= max) - out.h = 2.0 + (in.b - in.r) / delta; // between cyan & yellow - else - out.h = 4.0 + (in.r - in.g) / delta; // between magenta & cyan - - out.h *= 60.0; // degrees - - if (out.h < 0.0) - out.h += 360.0; - - return out; - } - - hsv rgb2hsv(const std::string& str_clr_in) - { - wxColour clr(str_clr_in); - rgb in = { clr.Red() / 255.0, clr.Green() / 255.0, clr.Blue() / 255.0 }; - return rgb2hsv(in); - } - - - rgb hsv2rgb(hsv in) - { - double hh, p, q, t, ff; - long i; - rgb out; - - if (in.s <= 0.0) { // < is bogus, just shuts up warnings - out.r = in.v; - out.g = in.v; - out.b = in.v; - return out; - } - hh = in.h; - if (hh >= 360.0) hh -= 360.0;//hh = 0.0; - hh /= 60.0; - i = (long)hh; - ff = hh - i; - p = in.v * (1.0 - in.s); - q = in.v * (1.0 - (in.s * ff)); - t = in.v * (1.0 - (in.s * (1.0 - ff))); - - switch (i) { - case 0: - out.r = in.v; - out.g = t; - out.b = p; - break; - case 1: - out.r = q; - out.g = in.v; - out.b = p; - break; - case 2: - out.r = p; - out.g = in.v; - out.b = t; - break; - - case 3: - out.r = p; - out.g = q; - out.b = in.v; - break; - case 4: - out.r = t; - out.g = p; - out.b = in.v; - break; - case 5: - default: - out.r = in.v; - out.g = p; - out.b = q; - break; - } - return out; - } - - std::random_device rd; - -public: - - ColorGenerator() {} - ~ColorGenerator() {} - - double rand_val() - { - std::mt19937 rand_generator(rd()); - - // this value will be used for Saturation and Value - // to avoid extremely light/dark colors, take this value from range [0.65; 1.0] - std::uniform_real_distribution distrib(0.65, 1.0); - return distrib(rand_generator); - } - - - std::string get_opposite_color(const std::string& color) - { - std::string opp_color = ""; - - hsv hsv_clr = rgb2hsv(color); - hsv_clr.h += 65; // 65 instead 60 to avoid circle values - hsv_clr.s = rand_val(); - hsv_clr.v = rand_val(); - - rgb rgb_opp_color = hsv2rgb(hsv_clr); - - wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), (unsigned char)(rgb_opp_color.r * 255), (unsigned char)(rgb_opp_color.g * 255), (unsigned char)(rgb_opp_color.b * 255)); - opp_color = clr_str.ToStdString(); - - return opp_color; - } - - std::string get_opposite_color(const std::string& color_frst, const std::string& color_scnd) - { - std::string opp_color = ""; - - hsv hsv_frst = rgb2hsv(color_frst); - hsv hsv_scnd = rgb2hsv(color_scnd); - - double delta_h = fabs(hsv_frst.h - hsv_scnd.h); - double start_h = delta_h > 180 ? std::min(hsv_scnd.h, hsv_frst.h) : std::max(hsv_scnd.h, hsv_frst.h); - start_h += 5; // to avoid circle change of colors for 120 deg - if (delta_h < 180) - delta_h = 360 - delta_h; - - hsv hsv_opp = hsv{ start_h + 0.5 * delta_h, rand_val(), rand_val() }; - rgb rgb_opp_color = hsv2rgb(hsv_opp); - - wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), (unsigned char)(rgb_opp_color.r * 255), (unsigned char)(rgb_opp_color.g * 255), (unsigned char)(rgb_opp_color.b * 255)); - opp_color = clr_str.ToStdString(); - - return opp_color; - } -}; \ No newline at end of file +#endif // slic3r_GUI_DoubleSlider_Utils_hpp_ diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 6b210a708e..f02bcbe12d 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1386,13 +1386,8 @@ void ColourPicker::set_value(const boost::any& value, bool change_event) boost::any& ColourPicker::get_value() { auto colour = static_cast(window)->GetColour(); - if (colour == wxTransparentColour) - m_value = std::string(""); - else { - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - m_value = clr_str.ToStdString(); - } - return m_value; + m_value = (colour == wxTransparentColour) ? std::string("") : encode_color(ColorRGB(colour.Red(), colour.Green(), colour.Blue())); + return m_value; } void ColourPicker::msw_rescale() diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 8bdf4b8d80..a9812abf2a 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -39,7 +39,86 @@ using t_back_to_init = std::function; wxString double_to_string(double const value, const int max_precision = 4); wxString get_thumbnails_string(const std::vector& values); -class Field { +class UndoValueUIManager +{ + struct UndoValueUI { + // Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. + const ScalableBitmap* undo_bitmap{ nullptr }; + const wxString* undo_tooltip{ nullptr }; + // Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. + const ScalableBitmap* undo_to_sys_bitmap{ nullptr }; + const wxString* undo_to_sys_tooltip{ nullptr }; + // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. + const wxColour* label_color{ nullptr }; + // State of the blinker icon + bool blink{ false }; + + bool set_undo_bitmap(const ScalableBitmap* bmp) { + if (undo_bitmap != bmp) { + undo_bitmap = bmp; + return true; + } + return false; + } + + bool set_undo_to_sys_bitmap(const ScalableBitmap* bmp) { + if (undo_to_sys_bitmap != bmp) { + undo_to_sys_bitmap = bmp; + return true; + } + return false; + } + + bool set_label_colour(const wxColour* clr) { + if (label_color != clr) { + label_color = clr; + } + return false; + } + + bool set_undo_tooltip(const wxString* tip) { + if (undo_tooltip != tip) { + undo_tooltip = tip; + return true; + } + return false; + } + + bool set_undo_to_sys_tooltip(const wxString* tip) { + if (undo_to_sys_tooltip != tip) { + undo_to_sys_tooltip = tip; + return true; + } + return false; + } + }; + + UndoValueUI m_undo_ui; + +public: + UndoValueUIManager() {} + ~UndoValueUIManager() {} + + bool set_undo_bitmap(const ScalableBitmap* bmp) { return m_undo_ui.set_undo_bitmap(bmp); } + bool set_undo_to_sys_bitmap(const ScalableBitmap* bmp) { return m_undo_ui.set_undo_to_sys_bitmap(bmp); } + bool set_label_colour(const wxColour* clr) { return m_undo_ui.set_label_colour(clr); } + bool set_undo_tooltip(const wxString* tip) { return m_undo_ui.set_undo_tooltip(tip); } + bool set_undo_to_sys_tooltip(const wxString* tip) { return m_undo_ui.set_undo_to_sys_tooltip(tip); } + + // ui items used for revert line value + bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; } + const wxBitmap& undo_bitmap() const { return m_undo_ui.undo_bitmap->bmp(); } + const wxString* undo_tooltip() const { return m_undo_ui.undo_tooltip; } + const wxBitmap& undo_to_sys_bitmap() const { return m_undo_ui.undo_to_sys_bitmap->bmp(); } + const wxString* undo_to_sys_tooltip() const { return m_undo_ui.undo_to_sys_tooltip; } + const wxColour* label_color() const { return m_undo_ui.label_color; } + const bool blink() const { return m_undo_ui.blink; } + bool* get_blink_ptr() { return &m_undo_ui.blink; } +}; + + +class Field : public UndoValueUIManager +{ protected: // factory function to defer and enforce creation of derived type. virtual void PostInitialize(); @@ -137,49 +216,6 @@ public: return std::move(p); //!p; } - bool set_undo_bitmap(const ScalableBitmap *bmp) { - if (m_undo_bitmap != bmp) { - m_undo_bitmap = bmp; - return true; - } - return false; - } - - bool set_undo_to_sys_bitmap(const ScalableBitmap *bmp) { - if (m_undo_to_sys_bitmap != bmp) { - m_undo_to_sys_bitmap = bmp; - return true; - } - return false; - } - - bool set_label_colour(const wxColour *clr) { - if (m_label_color != clr) { - m_label_color = clr; - } - return false; - } - - bool set_undo_tooltip(const wxString *tip) { - if (m_undo_tooltip != tip) { - m_undo_tooltip = tip; - return true; - } - return false; - } - - bool set_undo_to_sys_tooltip(const wxString *tip) { - if (m_undo_to_sys_tooltip != tip) { - m_undo_to_sys_tooltip = tip; - return true; - } - return false; - } - - bool* get_blink_ptr() { - return &m_blink; - } - virtual void msw_rescale(); virtual void sys_color_changed(); @@ -191,26 +227,7 @@ public: static int def_width_wider() ; static int def_width_thinner() ; - const ScalableBitmap* undo_bitmap() { return m_undo_bitmap; } - const wxString* undo_tooltip() { return m_undo_tooltip; } - const ScalableBitmap* undo_to_sys_bitmap() { return m_undo_to_sys_bitmap; } - const wxString* undo_to_sys_tooltip() { return m_undo_to_sys_tooltip; } - const wxColour* label_color() { return m_label_color; } - const bool blink() { return m_blink; } - protected: - // Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. - const ScalableBitmap* m_undo_bitmap = nullptr; - const wxString* m_undo_tooltip = nullptr; - // Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. - const ScalableBitmap* m_undo_to_sys_bitmap = nullptr; - const wxString* m_undo_to_sys_tooltip = nullptr; - - bool m_blink{ false }; - - // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. - const wxColour* m_label_color = nullptr; - // current value boost::any m_value; // last maeningful value diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 7600ef4fa6..47402644f4 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -654,7 +654,7 @@ void FirmwareDialog::priv::perform_upload() } }) .on_message([ -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(__clang__) // clang complains when capturing constants. extra_verbose, #endif // __APPLE__ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 75ebd92551..367846f0c8 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -47,32 +47,6 @@ static EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(EMoveType::Retract) + id); } -static std::array decode_color(const std::string& color) { - static const float INV_255 = 1.0f / 255.0f; - - std::array ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - const char* c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - ret[j] = float(digit1 * 16 + digit2) * INV_255; - } - } - return ret; -} - -static std::vector> decode_colors(const std::vector& colors) { - std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f, 1.0f }); - for (size_t i = 0; i < colors.size(); ++i) { - output[i] = decode_color(colors[i]); - } - return output; -} - // Round to a bin with minimum two digits resolution. // Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. static float round_to_bin(const float value) @@ -186,27 +160,47 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); } -GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const +#if ENABLE_PREVIEW_LAYER_TIME +float GCodeViewer::Extrusions::Range::step_size(EType type) const +{ + switch (type) + { + default: + case EType::Linear: { return (max > min) ? (max - min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } + case EType::Logarithmic: { return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } + } +} + +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const +#else +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const +#endif // ENABLE_PREVIEW_LAYER_TIME { // Input value scaled to the colors range +#if ENABLE_PREVIEW_LAYER_TIME + float global_t = 0.0f; + const float step = step_size(type); + if (step > 0.0f) { + switch (type) + { + default: + case EType::Linear: { global_t = (value > min) ? (value - min) / step : 0.0f; break; } + case EType::Logarithmic: { global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f; break; } + } + } +#else const float step = step_size(); const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f +#endif // ENABLE_PREVIEW_LAYER_TIME const size_t color_max_idx = Range_Colors.size() - 1; // Compute the two colors just below (low) and above (high) the input value - const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); + const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); - // Compute how far the value is between the low and high colors so that they can be interpolated - const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); - // Interpolate between the low and high colors to find exactly which color the input value should get - Color ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - for (unsigned int i = 0; i < 3; ++i) { - ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); - } - return ret; + return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast(color_low_idx)); } GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { @@ -227,7 +221,11 @@ void GCodeViewer::SequentialRangeCap::reset() { void GCodeViewer::SequentialView::Marker::init() { m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_model.set_color({ 1.0f, 1.0f, 1.0f, 0.5f }); +#else m_model.set_color(-1, { 1.0f, 1.0f, 1.0f, 0.5f }); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) @@ -236,7 +234,7 @@ void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& positi m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); } -void GCodeViewer::SequentialView::Marker::render() const +void GCodeViewer::SequentialView::Marker::render() { if (!m_visible) return; @@ -266,7 +264,7 @@ void GCodeViewer::SequentialView::Marker::render() const static size_t last_text_length = 0; ImGuiWrapper& imgui = *wxGetApp().imgui(); - Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.25f); @@ -279,8 +277,8 @@ void GCodeViewer::SequentialView::Marker::render() const imgui.text(std::string(buf)); // force extra frame to automatically update window size - float width = ImGui::GetWindowWidth(); - size_t length = strlen(buf); + const float width = ImGui::GetWindowWidth(); + const size_t length = strlen(buf); if (width != last_window_width || length != last_text_length) { last_window_width = width; last_text_length = length; @@ -351,11 +349,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; @@ -471,7 +469,7 @@ void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() m_file.close(); } -void GCodeViewer::SequentialView::render(float legend_height) const +void GCodeViewer::SequentialView::render(float legend_height) { marker.render(); float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); @@ -480,7 +478,7 @@ void GCodeViewer::SequentialView::render(float legend_height) const gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); } -const std::vector GCodeViewer::Extrusion_Role_Colors {{ +const std::vector GCodeViewer::Extrusion_Role_Colors{ { { 0.90f, 0.70f, 0.70f, 1.0f }, // erNone { 1.00f, 0.90f, 0.30f, 1.0f }, // erPerimeter { 1.00f, 0.49f, 0.22f, 1.0f }, // erExternalPerimeter @@ -499,7 +497,7 @@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed }}; -const std::vector GCodeViewer::Options_Colors {{ +const std::vector GCodeViewer::Options_Colors{ { { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams @@ -509,7 +507,7 @@ const std::vector GCodeViewer::Options_Colors {{ { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes }}; -const std::vector GCodeViewer::Travel_Colors {{ +const std::vector GCodeViewer::Travel_Colors{ { { 0.219f, 0.282f, 0.609f, 1.0f }, // Move { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude { 0.505f, 0.064f, 0.028f, 1.0f } // Retract @@ -517,7 +515,7 @@ const std::vector GCodeViewer::Travel_Colors {{ #if 1 // Normal ranges -const std::vector GCodeViewer::Range_Colors {{ +const std::vector GCodeViewer::Range_Colors{ { { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish { 0.075f, 0.349f, 0.522f, 1.0f }, { 0.110f, 0.533f, 0.569f, 1.0f }, @@ -532,7 +530,7 @@ const std::vector GCodeViewer::Range_Colors {{ }}; #else // Detailed ranges -const std::vector GCodeViewer::Range_Colors{ { +const std::vector GCodeViewer::Range_Colors{ { { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish { 0.5f * (0.043f + 0.075f), 0.5f * (0.173f + 0.349f), 0.5f * (0.478f + 0.522f), 1.0f }, { 0.075f, 0.349f, 0.522f, 1.0f }, @@ -557,8 +555,8 @@ const std::vector GCodeViewer::Range_Colors{ { } }; #endif -const GCodeViewer::Color GCodeViewer::Wipe_Color = { 1.0f, 1.0f, 0.0f, 1.0f }; -const GCodeViewer::Color GCodeViewer::Neutral_Color = { 0.25f, 0.25f, 0.25f, 1.0f }; +const ColorRGBA GCodeViewer::Wipe_Color = ColorRGBA::YELLOW(); +const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); GCodeViewer::GCodeViewer() { @@ -586,14 +584,16 @@ void GCodeViewer::init() case EMoveType::Retract: case EMoveType::Unretract: case EMoveType::Seam: { -// if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { -// buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel; -// buffer.shader = "gouraud_light_instanced"; -// buffer.model.model.init_from(diamond(16)); -// buffer.model.color = option_color(type); -// buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel; -// } -// else { +#if !DISABLE_GCODEVIEWER_INSTANCED_MODELS + if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel; + buffer.shader = "gouraud_light_instanced"; + buffer.model.model.init_from(diamond(16)); + buffer.model.color = option_color(type); + buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel; + } + else { +#endif // !DISABLE_GCODEVIEWER_INSTANCED_MODELS buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; buffer.shader = "gouraud_light"; @@ -601,8 +601,10 @@ void GCodeViewer::init() buffer.model.data = diamond(16); buffer.model.color = option_color(type); buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel; -// } - break; +#if !DISABLE_GCODEVIEWER_INSTANCED_MODELS + } +#endif // !DISABLE_GCODEVIEWER_INSTANCED_MODELS + break; } case EMoveType::Wipe: case EMoveType::Extrude: { @@ -729,14 +731,17 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) // update tool colors from config stored in the gcode - m_tool_colors = decode_colors(gcode_result.extruder_colors); + decode_colors(gcode_result.extruder_colors, m_tool_colors); else // update tool colors - m_tool_colors = decode_colors(str_tool_colors); + decode_colors(str_tool_colors, m_tool_colors); + + ColorRGBA default_color; + decode_color("#FF8000", default_color); // ensure there are enough colors defined while (m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) - m_tool_colors.push_back(decode_color("#FF8000")); + m_tool_colors.push_back(default_color); // update ranges for coloring / legend m_extrusions.reset_ranges(); @@ -770,19 +775,37 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v } } +#if ENABLE_PREVIEW_LAYER_TIME + for (size_t i = 0; i < gcode_result.print_statistics.modes.size(); ++i) { + m_layers_times[i] = gcode_result.print_statistics.modes[i].layers_times; + } + + for (size_t i = 0; i < m_layers_times.size(); ++i) { + for (float time : m_layers_times[i]) { + m_extrusions.ranges.layer_time[i].update_from(time); + } + } +#endif // ENABLE_PREVIEW_LAYER_TIME + #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS // update buffers' render paths +#if ENABLE_PREVIEW_LAYOUT + refresh_render_paths(false, false); +#else refresh_render_paths(); +#endif // ENABLE_PREVIEW_LAYOUT log_memory_used("Refreshed G-code extrusion paths, "); } +#if !ENABLE_PREVIEW_LAYOUT void GCodeViewer::refresh_render_paths() { refresh_render_paths(false, false); } +#endif // !ENABLE_PREVIEW_LAYOUT void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) { @@ -800,7 +823,7 @@ void GCodeViewer::reset() m_paths_bounding_box = BoundingBoxf3(); m_max_bounding_box = BoundingBoxf3(); m_max_print_height = 0.0f; - m_tool_colors = std::vector(); + m_tool_colors = std::vector(); m_extruders_count = 0; m_extruder_ids = std::vector(); m_filament_diameters = std::vector(); @@ -811,12 +834,20 @@ void GCodeViewer::reset() m_layers_z_range = { 0, 0 }; m_roles = std::vector(); m_print_statistics.reset(); +#if ENABLE_PREVIEW_LAYER_TIME + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + m_layers_times[i] = std::vector(); + } +#endif // ENABLE_PREVIEW_LAYER_TIME m_custom_gcode_per_print_z = std::vector(); m_sequential_view.gcode_window.reset(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); #endif // ENABLE_GCODE_VIEWER_STATISTICS m_contained_in_bed = true; +#if ENABLE_PREVIEW_LAYOUT + m_legend_resizer.reset(); +#endif // ENABLE_PREVIEW_LAYOUT } void GCodeViewer::render() @@ -926,7 +957,9 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); +#if !ENABLE_PREVIEW_LAYOUT flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); +#endif // !ENABLE_PREVIEW_LAYOUT return flags; } @@ -947,7 +980,9 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); +#if !ENABLE_PREVIEW_LAYOUT enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); +#endif // !ENABLE_PREVIEW_LAYOUT } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) @@ -978,7 +1013,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const return; // collect color information to generate materials - std::vector colors; + std::vector colors; for (const RenderPath& path : t_buffer.render_paths) { colors.push_back(path.color); } @@ -1000,10 +1035,10 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); unsigned int colors_count = 1; - for (const Color& color : colors) { + for (const ColorRGBA& color : colors) { fprintf(fp, "\nnewmtl material_%d\n", colors_count++); fprintf(fp, "Ka 1 1 1\n"); - fprintf(fp, "Kd %g %g %g\n", color[0], color[1], color[2]); + fprintf(fp, "Kd %g %g %g\n", color.r(), color.g(), color.b()); fprintf(fp, "Ks 0 0 0\n"); } @@ -1064,7 +1099,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); @@ -1399,13 +1434,30 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) }; // format data into the buffers to be rendered as batched model - auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::Geometry& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { const double width = static_cast(1.5f * curr.width); const double height = static_cast(1.5f * curr.height); const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast(), Vec3d::Zero(), { width, width, height }); const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // append vertices + const size_t vertices_count = data.vertices_count(); + for (size_t i = 0; i < vertices_count; ++i) { + // append position + const Vec3d position = trafo * data.extract_position_3(i).cast(); + vertices.push_back(float(position.x())); + vertices.push_back(float(position.y())); + vertices.push_back(float(position.z())); + + // append normal + const Vec3d normal = normal_matrix * data.extract_normal_3(i).cast(); + vertices.push_back(float(normal.x())); + vertices.push_back(float(normal.y())); + vertices.push_back(float(normal.z())); + } +#else for (const auto& entity : data.entities) { // append vertices for (size_t i = 0; i < entity.positions.size(); ++i) { @@ -1422,6 +1474,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) vertices.push_back(static_cast(normal.z())); } } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // append instance position instances.push_back(curr.position.x()); @@ -1431,12 +1484,19 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) instances_ids.push_back(move_id); }; - auto add_indices_as_model_batch = [](const GLModel::InitializationData& data, IndexBuffer& indices, IBufferType base_index) { + auto add_indices_as_model_batch = [](const GLModel::Geometry& data, IndexBuffer& indices, IBufferType base_index) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + const size_t indices_count = data.indices_count(); + for (size_t i = 0; i < indices_count; ++i) { + indices.push_back(static_cast(data.extract_ushort_index(i) + base_index)); + } +#else for (const auto& entity : data.entities) { for (size_t i = 0; i < entity.indices.size(); ++i) { indices.push_back(static_cast(entity.indices[i] + base_index)); } } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL }; #if ENABLE_GCODE_VIEWER_STATISTICS @@ -2017,7 +2077,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) m_layers.append(z, { last_travel_s_id, move_id }); else - m_layers.get_endpoints().back().last = move_id; + m_layers.get_ranges().back().last = move_id; // extruder ids m_extruder_ids.emplace_back(move.extruder_id); // roles @@ -2026,7 +2086,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) } else if (move.type == EMoveType::Travel) { if (move_id - last_travel_s_id > 1 && !m_layers.empty()) - m_layers.get_endpoints().back().last = move_id; + m_layers.get_ranges().back().last = move_id; last_travel_s_id = move_id; } @@ -2113,8 +2173,13 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) const float depth = print.wipe_tower_data(extruders_count).depth; const float brim_width = print.wipe_tower_data(extruders_count).brim_width; +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + !print.is_step_done(psWipeTower), brim_width, initialized); +#else m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, !print.is_step_done(psWipeTower), brim_width, initialized); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL } } @@ -2131,9 +2196,9 @@ 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(); + volume->set_render_color(true); } } @@ -2144,7 +2209,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool #endif // ENABLE_GCODE_VIEWER_STATISTICS auto extrusion_color = [this](const Path& path) { - Color color; + ColorRGBA color; switch (m_view_type) { case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } @@ -2153,17 +2218,37 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: + case EViewType::LayerTimeLogarithmic: { + const Path::Sub_Path& sub_path = path.sub_paths.front(); + double z = static_cast(sub_path.first.position.z()); + const std::vector& zs = m_layers.get_zs(); + const std::vector& ranges = m_layers.get_ranges(); + size_t time_mode_id = static_cast(m_time_estimate_mode); + for (size_t i = 0; i < zs.size(); ++i) { + if (std::abs(zs[i] - z) < EPSILON) { + if (ranges[i].contains(sub_path.first.s_id)) { + color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i], + (m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic); + break; + } + } + } + break; + } +#endif // ENABLE_PREVIEW_LAYER_TIME case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { if (path.cp_color_id >= static_cast(m_tool_colors.size())) - color = { 0.5f, 0.5f, 0.5f, 1.0f }; + color = ColorRGBA::GRAY(); else color = m_tool_colors[path.cp_color_id]; break; } - default: { color = { 1.0f, 1.0f, 1.0f, 1.0f }; break; } + default: { color = ColorRGBA::WHITE(); break; } } return color; @@ -2177,7 +2262,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { auto in_layers_range = [this, min_id, max_id](size_t id) { - return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last; + return m_layers.get_range_at(min_id).first <= id && id <= m_layers.get_range_at(max_id).last; }; return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id); @@ -2202,8 +2287,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last; } - const size_t min_s_id = m_layers.get_endpoints_at(min_id).first; - const size_t max_s_id = m_layers.get_endpoints_at(max_id).last; + const size_t min_s_id = m_layers.get_range_at(min_id).first; + const size_t max_s_id = m_layers.get_range_at(max_id).last; return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) || (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id); @@ -2236,14 +2321,14 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { for (size_t id : buffer.model.instances.s_ids) { - if (id < m_layers.get_endpoints_at(m_layers_z_range[0]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id) + if (id < m_layers.get_range_at(m_layers_z_range[0]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) continue; global_endpoints.first = std::min(global_endpoints.first, id); global_endpoints.last = std::max(global_endpoints.last, id); if (top_layer_only) { - if (id < m_layers.get_endpoints_at(m_layers_z_range[1]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id) + if (id < m_layers.get_range_at(m_layers_z_range[1]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) continue; top_layer_endpoints.first = std::min(top_layer_endpoints.first, id); @@ -2362,7 +2447,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: @@ -2681,7 +2766,7 @@ void GCodeViewer::render_toolpaths() // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(! path.sizes.empty()); assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); + shader.set_uniform(uniform_color, path.color); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; @@ -2705,7 +2790,7 @@ void GCodeViewer::render_toolpaths() // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(! path.sizes.empty()); assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); + shader.set_uniform(uniform_color, path.color); glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_lines_calls_count; @@ -2723,7 +2808,7 @@ void GCodeViewer::render_toolpaths() // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(! path.sizes.empty()); assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); + shader.set_uniform(uniform_color, path.color); glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_triangles_calls_count; @@ -2745,7 +2830,11 @@ void GCodeViewer::render_toolpaths() } if (range.vbo > 0) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + buffer.model.model.set_color(range.color); +#else buffer.model.model.set_color(-1, range.color); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL buffer.model.model.render_instanced(range.vbo, range.count); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_instanced_models_calls_count; @@ -2985,7 +3074,11 @@ void GCodeViewer::render_legend(float& legend_height) const float max_height = 0.75f * static_cast(cnv_size.get_height()); const float child_height = 0.3333f * max_height; ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); +#if ENABLE_PREVIEW_LAYOUT + imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); +#else imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); +#endif // ENABLE_PREVIEW_LAYOUT enum class EItemType : unsigned char { @@ -2996,15 +3089,21 @@ void GCodeViewer::render_legend(float& legend_height) }; const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; +#if ENABLE_PREVIEW_LAYER_TIME + bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); +#else bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); +#endif // ENABLE_PREVIEW_LAYER_TIME const float icon_size = ImGui::GetTextLineHeight(); const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, + auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const ColorRGBA& color, const std::string& label, bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, double used_filament_m = 0.0, double used_filament_g = 0.0, std::function callback = nullptr) { @@ -3017,21 +3116,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; } } @@ -3083,7 +3182,25 @@ void GCodeViewer::render_legend(float& legend_height) } else { imgui.text(label); +#if ENABLE_TRAVEL_TIME + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + const float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); + } + else if (used_filament_m > 0.0) { +#else if (used_filament_m > 0.0) { +#endif // ENABLE_TRAVEL_TIME char buf[64]; ImGui::SameLine(offsets[0]); ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); @@ -3109,6 +3226,7 @@ void GCodeViewer::render_legend(float& legend_height) // single item use case append_range_item(0, range.min, decimals); else if (range.count == 2) { + // two items use case append_range_item(static_cast(Range_Colors.size()) - 1, range.max, decimals); append_range_item(0, range.min, decimals); } @@ -3120,6 +3238,39 @@ void GCodeViewer::render_legend(float& legend_height) } }; +#if ENABLE_PREVIEW_LAYER_TIME + auto append_time_range = [append_item](const Extrusions::Range& range, Extrusions::Range::EType type) { + auto append_range_item = [append_item](int i, float value) { + std::string str_value = get_time_dhms(value); + if (str_value == "0s") + str_value = "< 1s"; + append_item(EItemType::Rect, Range_Colors[i], str_value); + }; + + if (range.count == 1) + // single item use case + append_range_item(0, range.min); + else if (range.count == 2) { + // two items use case + append_range_item(static_cast(Range_Colors.size()) - 1, range.max); + append_range_item(0, range.min); + } + else { + float step_size = range.step_size(type); + for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { + float value = 0.0f; + switch (type) + { + default: + case Extrusions::Range::EType::Linear: { value = range.min + static_cast(i) * step_size; break; } + case Extrusions::Range::EType::Logarithmic: { value = ::exp(::log(range.min) + static_cast(i) * step_size); break; } + } + append_range_item(i, value); + } + } + }; +#endif // ENABLE_PREVIEW_LAYER_TIME + auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { size_t i = 0; for (; i < offsets.size(); i++) { @@ -3149,7 +3300,7 @@ void GCodeViewer::render_legend(float& legend_height) }; auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { - std::vector>> ret; + std::vector>> ret; ret.reserve(custom_gcode_per_print_z.size()); for (const auto& item : custom_gcode_per_print_z) { @@ -3168,8 +3319,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; @@ -3216,7 +3370,7 @@ void GCodeViewer::render_legend(float& legend_height) std::vector percents; std::vector used_filaments_m; std::vector used_filaments_g; - float max_percent = 0.0f; + float max_time_percent = 0.0f; if (m_view_type == EViewType::FeatureType) { // calculate offsets to align time/percentage data @@ -3227,7 +3381,7 @@ void GCodeViewer::render_legend(float& legend_height) auto [time, percent] = role_time_and_percent(role); times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); percents.push_back(percent); - max_percent = std::max(max_percent, percent); + max_time_percent = std::max(max_time_percent, percent); auto [used_filament_m, used_filament_g] = used_filament_per_role(role); used_filaments_m.push_back(used_filament_m); used_filaments_g.push_back(used_filament_g); @@ -3287,6 +3441,47 @@ void GCodeViewer::render_legend(float& legend_height) offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); } +#if ENABLE_PREVIEW_LAYOUT + // selection section + bool view_type_changed = false; + int old_view_type = static_cast(get_view_type()); + int view_type = old_view_type; + + if (!m_legend_resizer.dirty) + ImGui::SetNextItemWidth(-1.0f); + + ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f }); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f }); + imgui.combo("", { _u8L("Feature type"), + _u8L("Height (mm)"), + _u8L("Width (mm)"), + _u8L("Speed (mm/s)"), + _u8L("Fan speed (%)"), + _u8L("Temperature (°C)"), + _u8L("Volumetric flow rate (mm³/s)"), +#if ENABLE_PREVIEW_LAYER_TIME + _u8L("Layer time (linear)"), + _u8L("Layer time (logarithmic)"), +#endif // ENABLE_PREVIEW_LAYER_TIME + _u8L("Tool"), + _u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest); + ImGui::PopStyleColor(2); + + if (old_view_type != view_type) { + set_view_type(static_cast(view_type)); + wxGetApp().plater()->set_keep_current_preview_type(true); + wxGetApp().plater()->refresh_print(); + view_type_changed = true; + } + + // extrusion paths section -> title + if (m_view_type == EViewType::FeatureType) + append_headers({ _u8L(""), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); + else if (m_view_type == EViewType::Tool) + append_headers({ _u8L(""), _u8L("Used filament"), _u8L(""), _u8L("") }, offsets); + else + ImGui::Separator(); +#else // extrusion paths section -> title switch (m_view_type) { @@ -3295,58 +3490,81 @@ void GCodeViewer::render_legend(float& legend_height) append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); break; } - case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } - case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } - case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } - case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: - { + case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } + case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } + case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } + case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } + case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } + case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: { imgui.title(_u8L("Layer time (linear)")); break; } + case EViewType::LayerTimeLogarithmic: { imgui.title(_u8L("Layer time (logarithmic)")); break; } +#endif // ENABLE_PREVIEW_LAYER_TIME + case EViewType::Tool: { append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); break; } - case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } + case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } default: { break; } } +#endif // ENABLE_PREVIEW_LAYOUT +#if ENABLE_PREVIEW_LAYOUT + if (!view_type_changed) { +#endif // ENABLE_PREVIEW_LAYOUT // extrusion paths section -> items switch (m_view_type) { case EViewType::FeatureType: { +#if ENABLE_TRAVEL_TIME + max_time_percent = std::max(max_time_percent, time_mode.travel_time / time_mode.time); +#endif // ENABLE_TRAVEL_TIME + for (size_t i = 0; i < m_roles.size(); ++i) { ExtrusionRole role = m_roles[i]; if (role >= erCount) continue; const bool visible = is_visible(role); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { + visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths refresh_render_paths(false, false); wxGetApp().plater()->update_preview_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); +#if !ENABLE_PREVIEW_LAYOUT wxGetApp().plater()->update_preview_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT } ); } + +#if ENABLE_TRAVEL_TIME + if (m_buffers[buffer_id(EMoveType::Travel)].visible) + append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time(get_time_dhms(time_mode.travel_time)), + time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f); +#endif // ENABLE_TRAVEL_TIME + break; } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } - case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } - case EViewType::Tool: - { + case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } + case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } +#if ENABLE_PREVIEW_LAYER_TIME + case EViewType::LayerTimeLinear: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Linear); break; } + case EViewType::LayerTimeLogarithmic: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Logarithmic); break; } +#endif // ENABLE_PREVIEW_LAYER_TIME + case EViewType::Tool: { // shows only extruders actually used size_t i = 0; for (unsigned char extruder_id : m_extruder_ids) { append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); - i++; + ++i; } break; } @@ -3358,17 +3576,16 @@ void GCodeViewer::render_legend(float& legend_height) total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); } - const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + const bool need_scrollable = static_cast(total_items) * icon_size + (static_cast(total_items) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; // add scrollable region, if needed if (need_scrollable) ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); if (m_extruders_count == 1) { // single extruder use case - const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode + if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); - } else { for (int i = items_cnt; i >= 0; --i) { // create label for color change item @@ -3387,11 +3604,11 @@ void GCodeViewer::render_legend(float& legend_height) else { // multi extruder use case // shows only extruders actually used for (unsigned char i : m_extruder_ids) { - const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode + if (items_cnt == 0) + // There are no color changes, but there are some pause print or custom Gcode append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); - } else { for (int j = items_cnt; j >= 0; --j) { // create label for color change item @@ -3420,6 +3637,9 @@ void GCodeViewer::render_legend(float& legend_height) } default: { break; } } +#if ENABLE_PREVIEW_LAYOUT + } +#endif // ENABLE_PREVIEW_LAYOUT // partial estimated printing time section if (m_view_type == EViewType::ColorPrint) { @@ -3437,10 +3657,10 @@ void GCodeViewer::render_legend(float& legend_height) }; EType type; int extruder_id; - Color color1; - Color color2; + ColorRGBA color1; + ColorRGBA color2; Times times; - std::pair used_filament {0.0f, 0.0f}; + std::pair used_filament{ 0.0f, 0.0f }; }; using PartialTimes = std::vector; @@ -3449,7 +3669,7 @@ void GCodeViewer::render_legend(float& legend_height) std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; int extruders_count = wxGetApp().extruders_edited_cnt(); - std::vector last_color(extruders_count); + std::vector last_color(extruders_count); for (int i = 0; i < extruders_count; ++i) { last_color[i] = m_tool_colors[i]; } @@ -3461,8 +3681,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; @@ -3470,14 +3690,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; } @@ -3488,7 +3710,7 @@ void GCodeViewer::render_legend(float& legend_height) return items; }; - auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { + auto append_color_change = [&imgui](const ColorRGBA& color1, const ColorRGBA& color2, const std::array& offsets, const Times& times) { imgui.text(_u8L("Color change")); ImGui::SameLine(); @@ -3498,16 +3720,16 @@ void GCodeViewer::render_legend(float& legend_height) pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); + ImGuiWrapper::to_ImU32(color1)); pos.x += icon_size; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); + ImGuiWrapper::to_ImU32(color2)); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second - times.first))); }; - auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { + auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array& offsets, const Times& times, std::pair used_filament) { imgui.text(_u8L("Print")); ImGui::SameLine(); @@ -3517,7 +3739,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))); @@ -3565,7 +3787,7 @@ void GCodeViewer::render_legend(float& legend_height) ImGui::Spacing(); append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); - const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + const bool need_scrollable = static_cast(partial_times.size()) * icon_size + (static_cast(partial_times.size()) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; if (need_scrollable) // add scrollable region ImGui::BeginChild("events", { -1.0f, child_height }, false); @@ -3595,6 +3817,7 @@ void GCodeViewer::render_legend(float& legend_height) } } +#if !ENABLE_PREVIEW_LAYOUT // travel paths section if (m_buffers[buffer_id(EMoveType::Travel)].visible) { switch (m_view_type) @@ -3675,6 +3898,7 @@ void GCodeViewer::render_legend(float& legend_height) add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); } +#endif // !ENABLE_PREVIEW_LAYOUT // settings section bool has_settings = false; @@ -3756,7 +3980,7 @@ void GCodeViewer::render_legend(float& legend_height) if (can_show_mode_button(m_time_estimate_mode)) { switch (m_time_estimate_mode) { - case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } + case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]"; break; } default: { assert(false); break; } } @@ -3787,6 +4011,10 @@ void GCodeViewer::render_legend(float& legend_height) if (can_show_mode_button(mode)) { if (imgui.button(label)) { m_time_estimate_mode = mode; +#if ENABLE_PREVIEW_LAYER_TIME + if (m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic) + refresh_render_paths(false, false); +#endif // ENABLE_PREVIEW_LAYER_TIME imgui.set_requires_extra_frame(); } } @@ -3805,7 +4033,205 @@ void GCodeViewer::render_legend(float& legend_height) } } - legend_height = ImGui::GetCurrentWindow()->Size.y; +#if ENABLE_PREVIEW_LAYOUT + // toolbar section + auto toggle_button = [this, &imgui, icon_size](Preview::OptionType type, const std::string& name, + std::function draw_callback) { + auto is_flag_set = [](unsigned int flags, unsigned int flag) { + return (flags & (1 << flag)) != 0; + }; + + auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { + return active ? (flags | (1 << flag)) : (flags & ~(1 << flag)); + }; + + unsigned int flags = get_options_visibility_flags(); + unsigned int flag = static_cast(type); + bool active = is_flag_set(flags, flag); + + if (imgui.draw_radio_button(name, 1.5f * icon_size, active, draw_callback)) { + unsigned int new_flags = set_flag(flags, flag, !active); + set_options_visibility_from_flags(new_flags); + + const unsigned int diff_flags = flags ^ new_flags; + if (m_view_type == GCodeViewer::EViewType::Feedrate && is_flag_set(diff_flags, static_cast(Preview::OptionType::Travel))) + wxGetApp().plater()->refresh_print(); + else { + bool keep_first = m_sequential_view.current.first != m_sequential_view.global.first; + bool keep_last = m_sequential_view.current.last != m_sequential_view.global.last; + wxGetApp().plater()->get_current_canvas3D()->refresh_gcode_preview_render_paths(keep_first, keep_last); + } + wxGetApp().plater()->update_preview_moves_slider(); + } + + if (ImGui::IsItemHovered()) { + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(name); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + } + }; + +#if ENABLE_LEGEND_TOOLBAR_ICONS +// auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { +// const float margin = 3.0f; +// const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); +// window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); +// }; +// auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { +// const float margin = 3.0f; +// window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); +// }; + auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) { + ImGuiIO& io = ImGui::GetIO(); + const ImTextureID tex_id = io.Fonts->TexID; + const float tex_w = static_cast(io.Fonts->TexWidth); + const float tex_h = static_cast(io.Fonts->TexHeight); + const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id); + const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h }; + const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; + window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 1.0f })); + }; +#else + auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { + const float margin = 3.0f; + const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); + window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + }; + auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { + const float margin = 3.0f; + window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + }; +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::Spacing(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendTravel); +#else + toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + line_icon(window, pos, size, Travel_Colors[0]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendWipe); +#else + toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + line_icon(window, pos, size, Wipe_Color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendRetract); +#else + toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Retractions)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendDeretract); +#else + toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Unretractions)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendSeams); +#else + toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Seams)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendToolChanges); +#else + toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ToolChanges)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendColorChanges); +#else + toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ColorChanges)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendPausePrints); +#else + toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::PausePrints)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendCustomGCodes); +#else + toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendShells); +#else + toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [](ImGuiWindow& window, const ImVec2& pos, float size) { + const ImU32 color = ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 1.0f }); + const float margin = 3.0f; + const float proj = 0.25f * size; + window.DrawList->AddRect({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin - proj, pos.y + margin + proj }, color); + window.DrawList->AddLine({ pos.x + margin, pos.y + margin + proj }, { pos.x + margin + proj, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + margin + proj }, { pos.x + size - margin, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + size - margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); + window.DrawList->AddLine({ pos.x + margin + proj, pos.y + margin }, { pos.x + size - margin, pos.y + margin }, color); + window.DrawList->AddLine({ pos.x + size - margin, pos.y + margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + ImGui::SameLine(); +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendToolMarker); +#else + toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [](ImGuiWindow& window, const ImVec2& pos, float size) { + const ImU32 color = ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 0.8f }); + const float margin = 3.0f; + const ImVec2 p1(0.5f * (pos.x + pos.x + size), pos.y + size - margin); + const ImVec2 p2 = ImVec2(p1.x + 0.25f * size, p1.y - 0.25f * size); + const ImVec2 p3 = ImVec2(p1.x - 0.25f * size, p1.y - 0.25f * size); + window.DrawList->AddTriangleFilled(p1, p2, p3, color); + const float mid_x = 0.5f * (pos.x + pos.x + size); + window.DrawList->AddRectFilled({ mid_x - 0.09375f * size, p1.y - 0.25f * size }, { mid_x + 0.09375f * size, pos.y + margin }, color); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + }); + + bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth(); + if (m_legend_resizer.dirty || size_dirty != m_legend_resizer.dirty) { + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + m_legend_resizer.dirty = size_dirty; +#endif // ENABLE_PREVIEW_LAYOUT + + legend_height = ImGui::GetWindowHeight(); imgui.end(); ImGui::PopStyleVar(); @@ -3927,14 +4353,14 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) } } int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); - layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints); + layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_ranges(), Layers::Range); BOOST_LOG_TRIVIAL(trace) << label << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" << log_memory_info(); } } -GCodeViewer::Color GCodeViewer::option_color(EMoveType move_type) const +ColorRGBA GCodeViewer::option_color(EMoveType move_type) const { switch (move_type) { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index a67208f10b..ecebb26414 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -22,7 +22,6 @@ namespace GUI { class GCodeViewer { using IBufferType = unsigned short; - using Color = std::array; using VertexBuffer = std::vector; using MultiVertexBuffer = std::vector; using IndexBuffer = std::vector; @@ -31,12 +30,12 @@ class GCodeViewer using InstanceIdBuffer = std::vector; using InstancesOffsets = std::vector; - static const std::vector Extrusion_Role_Colors; - static const std::vector Options_Colors; - static const std::vector Travel_Colors; - static const std::vector Range_Colors; - static const Color Wipe_Color; - static const Color Neutral_Color; + static const std::vector Extrusion_Role_Colors; + static const std::vector Options_Colors; + static const std::vector Travel_Colors; + static const std::vector Range_Colors; + static const ColorRGBA Wipe_Color; + static const ColorRGBA Neutral_Color; enum class EOptionsColors : unsigned char { @@ -121,7 +120,7 @@ class GCodeViewer // vbo id unsigned int vbo{ 0 }; // Color to apply to the instances - Color color; + ColorRGBA color; }; std::vector ranges; @@ -243,7 +242,7 @@ class GCodeViewer // Index of the parent tbuffer unsigned char tbuffer_id; // Render path property - Color color; + ColorRGBA color; // Index of the buffer in TBuffer::indices unsigned int ibuffer_id; // Render path content @@ -263,12 +262,10 @@ class GCodeViewer bool operator() (const RenderPath &l, const RenderPath &r) const { if (l.tbuffer_id < r.tbuffer_id) return true; - for (int i = 0; i < 3; ++i) { - if (l.color[i] < r.color[i]) - return true; - else if (l.color[i] > r.color[i]) - return false; - } + if (l.color < r.color) + return true; + else if (l.color > r.color) + return false; return l.ibuffer_id < r.ibuffer_id; } }; @@ -299,9 +296,9 @@ class GCodeViewer struct Model { GLModel model; - Color color; + ColorRGBA color; InstanceVBuffer instances; - GLModel::InitializationData data; + GLModel::Geometry data; void reset(); }; @@ -364,7 +361,11 @@ class GCodeViewer } case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); } case ERenderPrimitiveType::BatchedModel: { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + return !model.data.vertices.empty() && !model.data.indices.empty() && +#else return model.data.vertices_count() > 0 && model.data.indices_count() && +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; } default: { return false; } @@ -384,6 +385,14 @@ class GCodeViewer { struct Range { +#if ENABLE_PREVIEW_LAYER_TIME + enum class EType : unsigned char + { + Linear, + Logarithmic + }; +#endif // ENABLE_PREVIEW_LAYER_TIME + float min; float max; unsigned int count; @@ -398,8 +407,13 @@ class GCodeViewer } void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; } +#if ENABLE_PREVIEW_LAYER_TIME + float step_size(EType type = EType::Linear) const; + ColorRGBA get_color_at(float value, EType type = EType::Linear) const; +#else float step_size() const { return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); } - Color get_color_at(float value) const; + ColorRGBA get_color_at(float value) const; +#endif // ENABLE_PREVIEW_LAYER_TIME }; struct Ranges @@ -416,6 +430,10 @@ class GCodeViewer Range volumetric_rate; // Color mapping by extrusion temperature. Range temperature; +#if ENABLE_PREVIEW_LAYER_TIME + // Color mapping by layer time. + std::array(PrintEstimatedStatistics::ETimeMode::Count)> layer_time; +#endif // ENABLE_PREVIEW_LAYER_TIME void reset() { height.reset(); @@ -424,6 +442,11 @@ class GCodeViewer fan_speed.reset(); volumetric_rate.reset(); temperature.reset(); +#if ENABLE_PREVIEW_LAYER_TIME + for (auto& range : layer_time) { + range.reset(); + } +#endif // ENABLE_PREVIEW_LAYER_TIME } }; @@ -443,42 +466,43 @@ class GCodeViewer class Layers { public: - struct Endpoints + struct Range { size_t first{ 0 }; size_t last{ 0 }; - bool operator == (const Endpoints& other) const { return first == other.first && last == other.last; } - bool operator != (const Endpoints& other) const { return !operator==(other); } + bool operator == (const Range& other) const { return first == other.first && last == other.last; } + bool operator != (const Range& other) const { return !operator==(other); } + bool contains(size_t id) const { return first <= id && id <= last; } }; private: std::vector m_zs; - std::vector m_endpoints; + std::vector m_ranges; public: - void append(double z, Endpoints endpoints) { + void append(double z, const Range& range) { m_zs.emplace_back(z); - m_endpoints.emplace_back(endpoints); + m_ranges.emplace_back(range); } void reset() { m_zs = std::vector(); - m_endpoints = std::vector(); + m_ranges = std::vector(); } size_t size() const { return m_zs.size(); } bool empty() const { return m_zs.empty(); } const std::vector& get_zs() const { return m_zs; } - const std::vector& get_endpoints() const { return m_endpoints; } - std::vector& get_endpoints() { return m_endpoints; } + const std::vector& get_ranges() const { return m_ranges; } + std::vector& get_ranges() { return m_ranges; } double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } - Endpoints get_endpoints_at(unsigned int id) const { return (id < m_endpoints.size()) ? m_endpoints[id] : Endpoints(); } + Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); } bool operator != (const Layers& other) const { if (m_zs != other.m_zs) return true; - if (m_endpoints != other.m_endpoints) + if (m_ranges != other.m_ranges) return true; return false; } @@ -491,7 +515,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; } @@ -612,7 +636,7 @@ public: bool is_visible() const { return m_visible; } void set_visible(bool visible) { m_visible = visible; } - void render() const; + void render(); }; class GCodeWindow @@ -668,7 +692,7 @@ public: GCodeWindow gcode_window; std::vector gcode_ids; - void render(float legend_height) const; + void render(float legend_height); }; enum class EViewType : unsigned char @@ -680,6 +704,10 @@ public: FanSpeed, Temperature, VolumetricRate, +#if ENABLE_PREVIEW_LAYER_TIME + LayerTimeLinear, + LayerTimeLogarithmic, +#endif // ENABLE_PREVIEW_LAYER_TIME Tool, ColorPrint, Count @@ -695,7 +723,7 @@ private: // bounding box of toolpaths + marker tools BoundingBoxf3 m_max_bounding_box; float m_max_print_height{ 0.0f }; - std::vector m_tool_colors; + std::vector m_tool_colors; Layers m_layers; std::array m_layers_z_range; std::vector m_roles; @@ -708,6 +736,14 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; +#if ENABLE_PREVIEW_LAYOUT + struct LegendResizer + { + bool dirty{ true }; + void reset() { dirty = true; } + }; + LegendResizer m_legend_resizer; +#endif // ENABLE_PREVIEW_LAYOUT PrintEstimatedStatistics m_print_statistics; PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; #if ENABLE_GCODE_VIEWER_STATISTICS @@ -716,6 +752,9 @@ private: std::array m_detected_point_sizes = { 0.0f, 0.0f }; GCodeProcessorResult::SettingsIds m_settings_ids; std::array m_sequential_range_caps; +#if ENABLE_PREVIEW_LAYER_TIME + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> m_layers_times; +#endif // ENABLE_PREVIEW_LAYER_TIME std::vector m_custom_gcode_per_print_z; @@ -731,7 +770,11 @@ public: void load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized); // recalculate ranges in dependence of what is visible and sets tool/print colors void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); +#if ENABLE_PREVIEW_LAYOUT + void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; +#else void refresh_render_paths(); +#endif // ENABLE_PREVIEW_LAYOUT void update_shells_color_by_extruder(const DynamicPrintConfig* config); void reset(); @@ -775,10 +818,16 @@ public: std::vector& get_custom_gcode_per_print_z() { return m_custom_gcode_per_print_z; } size_t get_extruders_count() { return m_extruders_count; } +#if ENABLE_PREVIEW_LAYOUT + void invalidate_legend() { m_legend_resizer.reset(); } +#endif // ENABLE_PREVIEW_LAYOUT + private: void load_toolpaths(const GCodeProcessorResult& gcode_result); void load_shells(const Print& print, bool initialized); +#if !ENABLE_PREVIEW_LAYOUT void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; +#endif // !ENABLE_PREVIEW_LAYOUT void render_toolpaths(); void render_shells(); void render_legend(float& legend_height); @@ -790,7 +839,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 b0026735d1..84cc2e5552 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -72,11 +72,17 @@ 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 } }; +#if ENABLE_GLBEGIN_GLEND_REMOVAL +static const Slic3r::ColorRGBA DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f, 1.0f }; +static const Slic3r::ColorRGBA ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f, 1.0f }; +static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f }; +#else +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 }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // Number of floats static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB @@ -100,49 +106,6 @@ float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleF #undef Convex #endif -Size::Size() - : m_width(0) - , m_height(0) -{ -} - -Size::Size(int width, int height, float scale_factor) - : m_width(width) - , m_height(height) - , m_scale_factor(scale_factor) -{ -} - -int Size::get_width() const -{ - return m_width; -} - -void Size::set_width(int width) -{ - m_width = width; -} - -int Size::get_height() const -{ - return m_height; -} - -void Size::set_height(int height) -{ - m_height = height; -} - -int Size::get_scale_factor() const -{ - return m_scale_factor; -} - -void Size::set_scale_factor(int scale_factor) -{ - m_scale_factor = scale_factor; -} - GLCanvas3D::LayersEditing::~LayersEditing() { if (m_z_texture_id != 0) { @@ -211,7 +174,7 @@ void GLCanvas3D::LayersEditing::set_enabled(bool enabled) float GLCanvas3D::LayersEditing::s_overlay_window_width; -void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const +void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) { if (!m_enabled) return; @@ -299,8 +262,15 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const imgui.end(); const Rect& bar_rect = get_bar_rect_viewport(canvas); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_profile.dirty = m_profile.old_bar_rect != bar_rect; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL render_active_object_annotations(canvas, bar_rect); render_profile(bar_rect); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_profile.old_bar_rect = bar_rect; + m_profile.dirty = false; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) @@ -374,7 +344,7 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con return ret; } -void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const +void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) { GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); if (shader == nullptr) @@ -392,6 +362,34 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); // Render the color bar +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (!m_profile.background.is_initialized() || m_profile.dirty) { + m_profile.background.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + const float l = bar_rect.get_left(); + const float r = bar_rect.get_right(); + const float t = bar_rect.get_top(); + const float b = bar_rect.get_bottom(); + init_data.add_vertex(Vec2f(l, b), Vec2f(0.0f, 0.0f)); + init_data.add_vertex(Vec2f(r, b), Vec2f(1.0f, 0.0f)); + init_data.add_vertex(Vec2f(r, t), Vec2f(1.0f, 1.0f)); + init_data.add_vertex(Vec2f(l, t), Vec2f(0.0f, 1.0f)); + + // indices + init_data.add_ushort_triangle(0, 1, 2); + init_data.add_ushort_triangle(2, 3, 0); + + m_profile.background.init_from(std::move(init_data)); + } + + m_profile.background.render(); +#else const float l = bar_rect.get_left(); const float r = bar_rect.get_right(); const float t = bar_rect.get_top(); @@ -404,12 +402,14 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); shader->stop_using(); } -void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const +void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) { //FIXME show some kind of legend. @@ -417,9 +417,60 @@ void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const return; // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - float scale_x = bar_rect.get_width() / (float)(1.12 * m_slicing_parameters->max_layer_height); - float scale_y = bar_rect.get_height() / m_object_max_z; - float x = bar_rect.get_left() + (float)m_slicing_parameters->layer_height * scale_x; + const float scale_x = bar_rect.get_width() / float(1.12 * m_slicing_parameters->max_layer_height); + const float scale_y = bar_rect.get_height() / m_object_max_z; + +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // Baseline + if (!m_profile.baseline.is_initialized() || m_profile.dirty) { + m_profile.baseline.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::USHORT }; + init_data.color = ColorRGBA::BLACK(); + init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; + init_data.add_vertex(Vec2f(x, bar_rect.get_bottom())); + init_data.add_vertex(Vec2f(x, bar_rect.get_top())); + + // indices + init_data.add_ushort_line(0, 1); + + m_profile.baseline.init_from(std::move(init_data)); + } + + if (!m_profile.profile.is_initialized() || m_profile.dirty || m_profile.old_layer_height_profile != m_layer_height_profile) { + m_profile.old_layer_height_profile = m_layer_height_profile; + m_profile.profile.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::UINT }; + init_data.color = ColorRGBA::BLUE(); + init_data.vertices.reserve(m_layer_height_profile.size() * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(m_layer_height_profile.size() * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + indices + for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) { + init_data.add_vertex(Vec2f(bar_rect.get_left() + float(m_layer_height_profile[i + 1]) * scale_x, + bar_rect.get_bottom() + float(m_layer_height_profile[i]) * scale_y)); + init_data.add_uint_index(i / 2); + } + + m_profile.profile.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + m_profile.baseline.render(); + m_profile.profile.render(); + shader->stop_using(); + } +#else + const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; // Baseline glsafe(::glColor3f(0.0f, 0.0f, 0.0f)); @@ -434,6 +485,7 @@ void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const for (unsigned int i = 0; i < m_layer_height_profile.size(); i += 2) ::glVertex2f(bar_rect.get_left() + (float)m_layer_height_profile[i + 1] * scale_x, bar_rect.get_bottom() + (float)m_layer_height_profile[i] * scale_y); glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes) @@ -751,9 +803,42 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas static ImVec2 size(0.0f, 0.0f); auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) { + auto calc_cursor_height = []() { + float ret = 16.0f; +#ifdef _WIN32 + // see: https://forums.codeguru.com/showthread.php?449040-get-the-system-current-cursor-size + // this code is not perfect because it returns a maximum height equal to 31 even if the cursor bitmap shown on screen is bigger + // but at least it gives the same result as wxWidgets in the settings tabs + ICONINFO ii; + if (::GetIconInfo((HICON)GetCursor(), &ii) != 0) { + BITMAP bitmap; + ::GetObject(ii.hbmMask, sizeof(BITMAP), &bitmap); + int width = bitmap.bmWidth; + int height = (ii.hbmColor == nullptr) ? bitmap.bmHeight / 2 : bitmap.bmHeight; + HDC dc = ::CreateCompatibleDC(nullptr); + if (dc != nullptr) { + if (::SelectObject(dc, ii.hbmMask) != nullptr) { + for (int i = 0; i < width; ++i) { + for (int j = 0; j < height; ++j) { + if (::GetPixel(dc, i, j) != RGB(255, 255, 255)) { + if (ret < float(j)) + ret = float(j); + } + } + } + ::DeleteDC(dc); + } + } + ::DeleteObject(ii.hbmColor); + ::DeleteObject(ii.hbmMask); + } +#endif // _WIN32 + return ret; + }; + const Size cnv_size = canvas.get_canvas_size(); - const float x = std::clamp((float)position.x(), 0.0f, (float)cnv_size.get_width() - wnd_size.x); - const float y = std::clamp((float)position.y() + 16.0f, 0.0f, (float)cnv_size.get_height() - wnd_size.y); + const float x = std::clamp(float(position.x()), 0.0f, float(cnv_size.get_width()) - wnd_size.x); + const float y = std::clamp(float(position.y()) + calc_cursor_height(), 0.0f, float(cnv_size.get_height()) - wnd_size.y); return Vec2f(x, y); }; @@ -794,16 +879,37 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons if (polygons.empty()) return; +#if !ENABLE_GLBEGIN_GLEND_REMOVAL size_t triangles_count = 0; for (const Polygon& poly : polygons) { triangles_count += poly.points.size() - 2; } const size_t vertices_count = 3 * triangles_count; +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL if (m_render_fill) { - GLModel::InitializationData fill_data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry fill_data; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::UINT }; + fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; + + // vertices + indices + const ExPolygons polygons_union = union_ex(polygons); + unsigned int vertices_counter = 0; + for (const ExPolygon& poly : polygons_union) { + const std::vector triangulation = triangulate_expolygon_3d(poly); + for (const Vec3d& v : triangulation) { + fill_data.add_vertex((Vec3f)(v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + fill_data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + } + + m_fill.init_from(std::move(fill_data)); +#else + GLModel::Geometry::Entity entity; + entity.type = GLModel::EPrimitiveType::Triangles; entity.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; entity.positions.reserve(vertices_count); entity.normals.reserve(vertices_count); @@ -826,12 +932,16 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons fill_data.entities.emplace_back(entity); m_fill.init_from(fill_data); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } - GLModel::InitializationData perimeter_data; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting +#else + GLModel::Geometry perimeter_data; for (const Polygon& poly : polygons) { - GLModel::InitializationData::Entity ent; - ent.type = GLModel::PrimitiveType::LineLoop; + GLModel::Geometry::Entity ent; + ent.type = GLModel::EPrimitiveType::LineLoop; ent.positions.reserve(poly.points.size()); ent.indices.reserve(poly.points.size()); unsigned int id_count = 0; @@ -845,14 +955,19 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons } m_perimeter.init_from(perimeter_data); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } void GLCanvas3D::SequentialPrintClearance::render() { - std::array FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; - std::array NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; + const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; + const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader == nullptr) return; @@ -863,7 +978,11 @@ void GLCanvas3D::SequentialPrintClearance::render() glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); +#else m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL m_perimeter.render(); m_fill.render(); @@ -1124,11 +1243,18 @@ ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) { +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (current_printer_technology() != ptSLA) + return; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + m_render_sla_auxiliaries = visible; for (GLVolume* vol : m_volumes.volumes) { +#if !ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL if (vol->composite_id.object_id == 1000) continue; // the wipe tower +#endif // !ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) && vol->composite_id.volume_id < 0) @@ -1139,9 +1265,14 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv) { for (GLVolume* vol : m_volumes.volumes) { +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (vol->is_wipe_tower) + vol->is_active = (visible && mo == nullptr); +#else if (vol->composite_id.object_id == 1000) { // wipe tower vol->is_active = (visible && mo == nullptr); } +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL else { if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) @@ -1166,6 +1297,7 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject } } } + if (visible && !mo) toggle_sla_auxiliaries_visibility(true, mo, instance_idx); @@ -1183,7 +1315,7 @@ void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx ModelInstance* instance = model_object->instances[inst_idx]; for (GLVolume* volume : m_volumes.volumes) { - if ((volume->object_idx() == (int)obj_idx) && (volume->instance_idx() == inst_idx)) + if (volume->object_idx() == (int)obj_idx && volume->instance_idx() == inst_idx) volume->printable = instance->printable; } } @@ -1441,13 +1573,13 @@ void GLCanvas3D::render() wxGetApp().imgui()->new_frame(); if (m_picking_enabled) { - if (m_rectangle_selection.is_dragging()) - // picking pass using rectangle selection - _rectangular_selection_picking_pass(); - else if (!m_volumes.empty()) - // regular picking pass - _picking_pass(); - } + if (m_rectangle_selection.is_dragging()) + // picking pass using rectangle selection + _rectangular_selection_picking_pass(); + else if (!m_volumes.empty()) + // regular picking pass + _picking_pass(); + } #if ENABLE_RENDER_PICKING_PASS if (!m_picking_enabled || !m_show_picking_texture) { @@ -1846,12 +1978,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re volume->is_modifier = !mvs->model_volume->is_model_part(); volume->set_color(color_from_model_volume(*mvs->model_volume)); // force update of render_color alpha channel - bool transparent = volume->color[3] < 1.0f; - if (transparent) - volume->force_transparent = true; - volume->set_render_color(); - if (transparent) - volume->force_transparent = false; + volume->set_render_color(volume->color.is_transparent()); // updates volumes transformations volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); @@ -2027,9 +2154,15 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re float depth = print->wipe_tower_data(extruders_count).depth; float brim_width = print->wipe_tower_data(extruders_count).brim_width; +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width, m_initialized); +#else int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), brim_width, m_initialized); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } @@ -2118,12 +2251,21 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, co request_extra_frame(); } +#if ENABLE_PREVIEW_LAYOUT +void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) +{ + m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); + set_as_dirty(); + request_extra_frame(); +} +#else void GLCanvas3D::refresh_gcode_preview_render_paths() { m_gcode_viewer.refresh_render_paths(); set_as_dirty(); request_extra_frame(); } +#endif // ENABLE_PREVIEW_LAYOUT void GLCanvas3D::load_sla_preview() { @@ -2427,12 +2569,16 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'i': { _update_camera_zoom(1.0); break; } case 'K': case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; } - case 'L': - case 'l': { - if (!m_main_toolbar.is_enabled()) { + case 'L': + case 'l': { + if (!m_main_toolbar.is_enabled()) { +#if ENABLE_PREVIEW_LAYOUT + show_legend(!is_legend_shown()); +#else m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); m_dirty = true; wxGetApp().plater()->update_preview_bottom_toolbar(); +#endif // ENABLE_PREVIEW_LAYOUT } break; } @@ -2927,6 +3073,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); m_mouse.set_start_position_3D_as_invalid(); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS return; } @@ -2934,6 +3083,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); m_mouse.set_start_position_3D_as_invalid(); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS return; } @@ -2941,6 +3093,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); m_mouse.set_start_position_3D_as_invalid(); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS return; } @@ -2948,6 +3103,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); m_mouse.set_start_position_3D_as_invalid(); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS return; } @@ -3001,6 +3159,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + handle_sidebar_focus_event("", false); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS return; } @@ -3015,15 +3176,31 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_move_start_threshold_position_2D_as_invalid(); } +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + if (evt.ButtonDown()) { + std::string curr_sidebar_field = m_sidebar_field; + handle_sidebar_focus_event("", false); + if (boost::algorithm::istarts_with(curr_sidebar_field, "layer")) + // restore visibility of layers hints after left clicking on the 3D scene + m_sidebar_field = curr_sidebar_field; + if (wxWindow::FindFocus() != m_canvas) + // Grab keyboard focus on any mouse click event. + m_canvas->SetFocus(); + } +#else if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas) // Grab keyboard focus on any mouse click event. m_canvas->SetFocus(); +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS if (evt.Entering()) { //#if defined(__WXMSW__) || defined(__linux__) // // On Windows and Linux needs focus in order to catch key events +#if !ENABLE_OBJECT_MANIPULATOR_FOCUS // Set focus in order to remove it from sidebar fields +#endif // !ENABLE_OBJECT_MANIPULATOR_FOCUS if (m_canvas != nullptr) { +#if !ENABLE_OBJECT_MANIPULATOR_FOCUS // Only set focus, if the top level window of this canvas is active. auto p = dynamic_cast(evt.GetEventObject()); while (p->GetParent()) @@ -3031,6 +3208,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) auto *top_level_wnd = dynamic_cast(p); if (top_level_wnd && top_level_wnd->IsActive()) m_canvas->SetFocus(); +#endif // !ENABLE_OBJECT_MANIPULATOR_FOCUS m_mouse.position = pos.cast(); m_tooltip_enabled = false; // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while @@ -3413,9 +3591,15 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) model_object->invalidate_bounding_box(); } } +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + else if (v->is_wipe_tower) + // Move a wipe tower proxy. + wipe_tower_origin = v->get_volume_offset(); +#else else if (object_idx == 1000) // Move a wipe tower proxy. wipe_tower_origin = v->get_volume_offset(); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL } // Fixes flying instances @@ -3475,11 +3659,18 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) Selection::EMode selection_mode = m_selection.get_mode(); for (const GLVolume* v : m_volumes.volumes) { +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (v->is_wipe_tower) { +#else int object_idx = v->object_idx(); if (object_idx == 1000) { // the wipe tower +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL Vec3d offset = v->get_volume_offset(); post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); } +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int object_idx = v->object_idx(); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; @@ -3673,7 +3864,6 @@ void GLCanvas3D::update_gizmos_on_off_state() void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) { m_sidebar_field = focus_on ? opt_key : ""; - if (!m_sidebar_field.empty()) m_gizmos.reset_all_states(); @@ -3759,6 +3949,9 @@ void GLCanvas3D::set_cursor(ECursorType type) void GLCanvas3D::msw_rescale() { +#if ENABLE_PREVIEW_LAYOUT + m_gcode_viewer.invalidate_legend(); +#endif // ENABLE_PREVIEW_LAYOUT } void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() @@ -3988,8 +4181,11 @@ bool GLCanvas3D::_render_search_list(float pos_x) action_taken = true; else sidebar.jump_to_option(selected);*/ - if (selected != 9999) + if (selected != 9999) { + imgui->end(); // end imgui before the jump to option sidebar.jump_to_option(selected); + return true; + } action_taken = true; } @@ -4103,9 +4299,6 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const return ret; }; - static const std::array orange = { 0.923f, 0.504f, 0.264f, 1.0f }; - static const std::array gray = { 0.64f, 0.64f, 0.64f, 1.0f }; - GLVolumePtrs visible_volumes; for (GLVolume* vol : volumes.volumes) { @@ -4160,7 +4353,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; @@ -4814,19 +5007,33 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be // The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum // A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any - if (include_gizmos && m_gizmos.is_running()) - { - BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); - Vec3d sel_bb_center = sel_bb.center(); - Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); + if (include_gizmos && m_gizmos.is_running()) { + const BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); + const Vec3d sel_bb_center = sel_bb.center(); + const Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); } - bb.merge(include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume()); + const BoundingBoxf3 bed_bb = include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume(); + bb.merge(bed_bb); if (!m_main_toolbar.is_enabled()) bb.merge(m_gcode_viewer.get_max_bounding_box()); + // clamp max bb size with respect to bed bb size + if (!m_picking_enabled) { + static const double max_scale_factor = 1.5; + const Vec3d bb_size = bb.size(); + const Vec3d bed_bb_size = bed_bb.size(); + if (bb_size.x() > max_scale_factor * bed_bb_size.x() || + bb_size.y() > max_scale_factor * bed_bb_size.y() || + bb_size.z() > max_scale_factor * bed_bb_size.z()) { + const Vec3d bed_bb_center = bed_bb.center(); + const Vec3d extend_by = max_scale_factor * bed_bb_size; + bb = BoundingBoxf3(bed_bb_center - extend_by, bed_bb_center + extend_by); + } + } + return bb; } @@ -4891,19 +5098,20 @@ void GLCanvas3D::_picking_pass() int volume_id = -1; int gizmo_id = -1; - GLubyte color[4] = { 0, 0, 0, 0 }; + std::array color = { 0, 0, 0, 0 }; const Size& cnv_size = get_canvas_size(); bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height(); if (inside) { - glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); + glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position.y() - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color.data())); if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) { // Only non-interpolated colors are valid, those have their lowest three bits zeroed. // we reserve color = (0,0,0) for occluders (as the printbed) // volumes' id are shifted by 1 // see: _render_volumes_for_picking() - volume_id = color[0] + (color[1] << 8) + (color[2] << 16) - 1; + unsigned int id = picking_encode(color[0], color[1], color[2]); + volume_id = id - 1; // gizmos' id are instead properly encoded by the color - gizmo_id = color[0] + (color[1] << 8) + (color[2] << 16); + gizmo_id = id; } } if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { @@ -4996,7 +5204,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() _update_volumes_hover_state(); } -void GLCanvas3D::_render_background() const +void GLCanvas3D::_render_background() { bool use_error_color = false; if (wxGetApp().is_editor()) { @@ -5018,23 +5226,50 @@ void GLCanvas3D::_render_background() const // Draws a bottom to top gradient over the complete screen. glsafe(::glDisable(GL_DEPTH_TEST)); - ::glBegin(GL_QUADS); - if (use_error_color) - ::glColor3fv(ERROR_BG_DARK_COLOR); - else - ::glColor3fv(DEFAULT_BG_DARK_COLOR); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + const ColorRGBA bottom_color = use_error_color ? ERROR_BG_DARK_COLOR : DEFAULT_BG_DARK_COLOR; + if (!m_background.is_initialized()) { + m_background.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex(Vec2f(-1.0f, -1.0f), Vec2f(0.0f, 0.0f)); + init_data.add_vertex(Vec2f(1.0f, -1.0f), Vec2f(1.0f, 0.0f)); + init_data.add_vertex(Vec2f(1.0f, 1.0f), Vec2f(1.0f, 1.0f)); + init_data.add_vertex(Vec2f(-1.0f, 1.0f), Vec2f(0.0f, 1.0f)); + + // indices + init_data.add_ushort_triangle(0, 1, 2); + init_data.add_ushort_triangle(2, 3, 0); + + m_background.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("background"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("top_color", use_error_color ? ERROR_BG_LIGHT_COLOR : DEFAULT_BG_LIGHT_COLOR); + shader->set_uniform("bottom_color", bottom_color); + + m_background.render(); + shader->stop_using(); + } +#else + ::glBegin(GL_QUADS); + ::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()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glEnable(GL_DEPTH_TEST)); @@ -5120,6 +5355,9 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + m_volumes.set_show_non_manifold_edges(!m_gizmos.is_hiding_instances() && m_gizmos.get_current_type() != GLGizmosManager::Simplify); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); if (shader != nullptr) { @@ -5150,7 +5388,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) // before transparent objects are rendered. Otherwise they would not be // visible when inside modifier meshes etc. { - const GLGizmosManager& gm = get_gizmos_manager(); + GLGizmosManager& gm = get_gizmos_manager(); // GLGizmosManager::EType type = gm.get_current_type(); if (dynamic_cast(gm.get_current())) { shader->stop_using(); @@ -5177,7 +5415,7 @@ void GLCanvas3D::_render_gcode() m_gcode_viewer.render(); } -void GLCanvas3D::_render_selection() const +void GLCanvas3D::_render_selection() { float scale_factor = 1.0; #if ENABLE_RETINA_GL @@ -5208,7 +5446,7 @@ void GLCanvas3D::_render_sequential_clearance() } #if ENABLE_RENDER_SELECTION_CENTER -void GLCanvas3D::_render_selection_center() const +void GLCanvas3D::_render_selection_center() { m_selection.render_center(m_gizmos.is_dragging()); } @@ -5314,8 +5552,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)); @@ -5330,13 +5566,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(); } } @@ -5458,28 +5690,78 @@ void GLCanvas3D::_render_view_toolbar() const } #if ENABLE_SHOW_CAMERA_TARGET -void GLCanvas3D::_render_camera_target() const +void GLCanvas3D::_render_camera_target() { - double half_length = 5.0; + static const double half_length = 5.0; glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glLineWidth(2.0f)); + +#if ENABLE_GLBEGIN_GLEND_REMOVAL + const Vec3d& target = wxGetApp().plater()->get_camera().get_target(); + bool target_changed = !m_camera_target.target.isApprox(target); + m_camera_target.target = target; + + for (int i = 0; i < 3; ++i) { + if (!m_camera_target.axis[i].is_initialized() || target_changed) { + m_camera_target.axis[i].reset(); + + GLModel::InitializationData init_data; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Lines; + entity.positions.reserve(2); + if (i == X) { + entity.positions.emplace_back(target.x() - half_length, target.y(), target.z()); + entity.positions.emplace_back(target.x() + half_length, target.y(), target.z()); + } + else if (i == Y) { + entity.positions.emplace_back(target.x(), target.y() - half_length, target.z()); + entity.positions.emplace_back(target.x(), target.y() + half_length, target.z()); + } + else { + entity.positions.emplace_back(target.x(), target.y(), target.z() - half_length); + entity.positions.emplace_back(target.x(), target.y(), target.z() + half_length); + } + entity.normals.reserve(2); + for (size_t j = 0; j < 2; ++j) { + entity.normals.emplace_back(Vec3f::UnitZ()); + } + + entity.indices.reserve(2); + entity.indices.emplace_back(0); + entity.indices.emplace_back(1); + + init_data.entities.emplace_back(entity); + m_camera_target.axis[i].init_from(init_data); + m_camera_target.axis[i].set_color(-1, (i == X) ? ColorRGBA::X() : (i == Y) ? ColorRGBA::Y() : ColorRGBA::Z()); + } + } + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + for (int i = 0; i < 3; ++i) { + m_camera_target.axis[i].render(); + } + shader->stop_using(); + } +#else ::glBegin(GL_LINES); - const Vec3d& target = m_camera.get_target(); + const Vec3d& target = wxGetApp().plater()->get_camera().get_target(); // draw line for x axis ::glColor3f(1.0f, 0.0f, 0.0f); - ::glVertex3d(target(0) - half_length, target(1), target(2)); - ::glVertex3d(target(0) + half_length, target(1), target(2)); + ::glVertex3d(target.x() - half_length, target.y(), target.z()); + ::glVertex3d(target.x() + half_length, target.y(), target.z()); // draw line for y axis ::glColor3f(0.0f, 1.0f, 0.0f); - ::glVertex3d(target(0), target(1) - half_length, target(2)); - ::glVertex3d(target(0), target(1) + half_length, target(2)); + ::glVertex3d(target.x(), target.y() - half_length, target.z()); + ::glVertex3d(target.x(), target.y() + half_length, target.z()); // draw line for z axis ::glColor3f(0.0f, 0.0f, 1.0f); - ::glVertex3d(target(0), target(1), target(2) - half_length); - ::glVertex3d(target(0), target(1), target(2) + half_length); + ::glVertex3d(target.x(), target.y(), target.z() - half_length); + ::glVertex3d(target.x(), target.y(), target.z() + half_length); glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } #endif // ENABLE_SHOW_CAMERA_TARGET @@ -5601,7 +5883,7 @@ void GLCanvas3D::_render_sla_slices() } } -void GLCanvas3D::_render_selection_sidebar_hints() const +void GLCanvas3D::_render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field); } @@ -5755,7 +6037,7 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) if (!print->has_skirt() && !print->has_brim()) return; - const std::array color = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish + const ColorRGBA color = ColorRGBA::GREENISH(); // number of skirt layers size_t total_layer_count = 0; @@ -5802,7 +6084,8 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) { - std::vector> tool_colors = _parse_colors(str_tool_colors); + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); struct Ctxt { @@ -5811,20 +6094,20 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool has_perimeters; bool has_infill; bool has_support; - const std::vector>* tool_colors; + const std::vector* tool_colors; bool is_single_material_print; int extruders_cnt; const std::vector* color_print_values; - static const std::array& color_perimeters() { static std::array color = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow - static const std::array& color_infill() { static std::array color = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish - static const std::array& color_support() { static std::array color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - static const std::array& color_pause_or_custom_code() { static std::array color = { 0.5f, 0.5f, 0.5f, 1.f }; return color; } // gray + static ColorRGBA color_perimeters() { return ColorRGBA::YELLOW(); } + static ColorRGBA color_infill() { return ColorRGBA::REDISH(); } + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } + static ColorRGBA color_pause_or_custom_code() { return ColorRGBA::GRAY(); } // For cloring by a tool, return a parsed color. bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; } - const std::array& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } // For coloring by a color_print(M600), return a parsed color. bool color_by_color_print() const { return color_print_values!=nullptr; } @@ -5964,7 +6247,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const std::array& color) { + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { // Allocate the volume before locking. GLVolume *volume = new GLVolume(color); volume->is_extrusion_path = true; @@ -6083,10 +6366,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); + { + for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) + if ((*ptr_it)->empty()) { + delete *ptr_it; + *ptr_it = nullptr; + } + m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); + } for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { GLVolume* v = m_volumes.volumes[i]; v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); @@ -6105,21 +6392,22 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con if (!print->is_step_done(psWipeTower)) return; - std::vector> tool_colors = _parse_colors(str_tool_colors); + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); struct Ctxt { const Print *print; - const std::vector>* tool_colors; + const std::vector* tool_colors; Vec2f wipe_tower_pos; float wipe_tower_angle; - static const std::array& color_support() { static std::array color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } // For cloring by a tool, return a parsed color. bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; } - const std::array& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } int volume_idx(int tool, int feature) const { return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; } @@ -6151,7 +6439,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); size_t grain_size = std::max(n_items / 128, size_t(1)); tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const std::array& color) { + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { auto *volume = new GLVolume(color); volume->is_extrusion_path = true; tbb::spin_mutex::scoped_lock lock; @@ -6243,10 +6531,14 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); + { + for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) + if ((*ptr_it)->empty()) { + delete *ptr_it; + *ptr_it = nullptr; + } + m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); + } for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { GLVolume* v = m_volumes.volumes[i]; v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); @@ -6270,7 +6562,7 @@ void GLCanvas3D::_load_sla_shells() return; auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, - const TriangleMesh& mesh, const std::array& color, bool outside_printer_detection_enabled) { + const TriangleMesh& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); #if ENABLE_SMOOTH_NORMALS @@ -6332,28 +6624,6 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) _set_warning_notification(warning, show); } -std::vector> GLCanvas3D::_parse_colors(const std::vector& colors) -{ - static const float INV_255 = 1.0f / 255.0f; - - std::vector> output(colors.size(), { 1.0f, 1.0f, 1.0f, 1.0f }); - for (size_t i = 0; i < colors.size(); ++i) { - const std::string& color = colors[i]; - const char* c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - output[i][j] = float(digit1 * 16 + digit2) * INV_255; - } - } - } - return output; -} - void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) { enum ErrorType{ diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 605d75ddd2..93cd8f4242 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -59,25 +59,24 @@ class RetinaHelper; class Size { - int m_width; - int m_height; - float m_scale_factor; + int m_width{ 0 }; + int m_height{ 0 }; + float m_scale_factor{ 1.0f }; public: - Size(); - Size(int width, int height, float scale_factor = 1.0); + Size() = default; + Size(int width, int height, float scale_factor = 1.0f) : m_width(width), m_height(height), m_scale_factor(scale_factor) {} - int get_width() const; - void set_width(int width); + int get_width() const { return m_width; } + void set_width(int width) { m_width = width; } - int get_height() const; - void set_height(int height); + int get_height() const { return m_height; } + void set_height(int height) { m_height = height; } - int get_scale_factor() const; - void set_scale_factor(int height); + float get_scale_factor() const { return m_scale_factor; } + void set_scale_factor(float factor) { m_scale_factor = factor; } }; - class RenderTimerEvent : public wxEvent { public: @@ -196,8 +195,8 @@ class GLCanvas3D }; static const float THICKNESS_BAR_WIDTH; - private: + private: bool m_enabled{ false }; unsigned int m_z_texture_id{ 0 }; // Not owned by LayersEditing. @@ -240,6 +239,18 @@ class GLCanvas3D int last_object_id{ -1 }; float last_z{ 0.0f }; LayerHeightEditActionType last_action{ LAYER_HEIGHT_EDIT_ACTION_INCREASE }; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + struct Profile + { + GLModel baseline; + GLModel profile; + GLModel background; + Rect old_bar_rect; + std::vector old_layer_height_profile; + bool dirty{ false }; + }; + Profile m_profile; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL LayersEditing() = default; ~LayersEditing(); @@ -254,7 +265,7 @@ class GLCanvas3D bool is_enabled() const; void set_enabled(bool enabled); - void render_overlay(const GLCanvas3D& canvas) const; + void render_overlay(const GLCanvas3D& canvas); void render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes); void adjust_layer_height_profile(); @@ -276,12 +287,11 @@ class GLCanvas3D private: bool is_initialized() const; void generate_layer_height_texture(); - void render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const; - void render_profile(const Rect& bar_rect) const; + void render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect); + void render_profile(const Rect& bar_rect); void update_slicing_parameters(); - static float thickness_bar_width(const GLCanvas3D &canvas); - + static float thickness_bar_width(const GLCanvas3D &canvas); }; struct Mouse @@ -601,6 +611,19 @@ private: } m_gizmo_highlighter; +#if ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_SHOW_CAMERA_TARGET + struct CameraTarget + { + std::array axis; + Vec3d target{ Vec3d::Zero() }; + }; + + CameraTarget m_camera_target; +#endif // ENABLE_SHOW_CAMERA_TARGET + GLModel m_background; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + public: explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed); ~GLCanvas3D(); @@ -731,7 +754,11 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); void load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); +#if ENABLE_PREVIEW_LAYOUT + void refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last); +#else void refresh_gcode_preview_render_paths(); +#endif // ENABLE_PREVIEW_LAYOUT void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } void load_sla_preview(); @@ -824,6 +851,11 @@ public: bool are_labels_shown() const { return m_labels.is_shown(); } void show_labels(bool show) { m_labels.show(show); } +#if ENABLE_PREVIEW_LAYOUT + bool is_legend_shown() const { return m_gcode_viewer.is_legend_enabled(); } + void show_legend(bool show) { m_gcode_viewer.enable_legend(show); m_dirty = true; } +#endif // ENABLE_PREVIEW_LAYOUT + bool is_using_slope() const { return m_slope.is_used(); } void use_slope(bool use) { m_slope.use(use); } void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); } @@ -901,15 +933,15 @@ private: void _picking_pass(); void _rectangular_selection_picking_pass(); - void _render_background() const; + void _render_background(); void _render_bed(bool bottom, bool show_axes); void _render_bed_for_picking(bool bottom); void _render_objects(GLVolumeCollection::ERenderType type); void _render_gcode(); - void _render_selection() const; + void _render_selection(); void _render_sequential_clearance(); #if ENABLE_RENDER_SELECTION_CENTER - void _render_selection_center() const; + void _render_selection_center(); #endif // ENABLE_RENDER_SELECTION_CENTER void _check_and_update_toolbar_icon_scale(); void _render_overlays(); @@ -921,10 +953,10 @@ private: void _render_collapse_toolbar() const; void _render_view_toolbar() const; #if ENABLE_SHOW_CAMERA_TARGET - void _render_camera_target() const; + void _render_camera_target(); #endif // ENABLE_SHOW_CAMERA_TARGET void _render_sla_slices(); - void _render_selection_sidebar_hints() const; + void _render_selection_sidebar_hints(); bool _render_undo_redo_stack(const bool is_undo, float pos_x); bool _render_search_list(float pos_x); bool _render_arrange_menu(float pos_x); @@ -981,8 +1013,6 @@ private: bool _deactivate_arrange_menu(); float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); } - - static std::vector> _parse_colors(const std::vector& colors); }; } // namespace GUI diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 422b654080..4d3be069ca 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -17,7 +17,329 @@ namespace Slic3r { namespace GUI { -size_t GLModel::InitializationData::vertices_count() const +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLModel::Geometry::add_vertex(const Vec2f& position) +{ + assert(format.vertex_layout == EVertexLayout::P2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); +} + +void GLModel::Geometry::add_vertex(const Vec2f& position, const Vec2f& tex_coord) +{ + assert(format.vertex_layout == EVertexLayout::P2T2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(tex_coord.x()); + vertices.emplace_back(tex_coord.y()); +} + +void GLModel::Geometry::add_vertex(const Vec3f& position) +{ + assert(format.vertex_layout == EVertexLayout::P3); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); +} + +void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal) +{ + assert(format.vertex_layout == EVertexLayout::P3N3); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(normal.x()); + vertices.emplace_back(normal.y()); + vertices.emplace_back(normal.z()); +} + +void GLModel::Geometry::add_ushort_index(unsigned short id) +{ + if (format.index_type != EIndexType::USHORT) { + assert(false); + return; + } + indices.resize(indices.size() + sizeof(unsigned short)); + ::memcpy(indices.data() + indices.size() - sizeof(unsigned short), &id, sizeof(unsigned short)); +} + +void GLModel::Geometry::add_uint_index(unsigned int id) +{ + if (format.index_type != EIndexType::UINT) { + assert(false); + return; + } + indices.resize(indices.size() + sizeof(unsigned int)); + ::memcpy(indices.data() + indices.size() - sizeof(unsigned int), &id, sizeof(unsigned int)); +} + +void GLModel::Geometry::add_ushort_line(unsigned short id1, unsigned short id2) +{ + if (format.index_type != EIndexType::USHORT) { + assert(false); + return; + } + indices.resize(indices.size() + 2 * sizeof(unsigned short)); + ::memcpy(indices.data() + indices.size() - 2 * sizeof(unsigned short), &id1, sizeof(unsigned short)); + ::memcpy(indices.data() + indices.size() - sizeof(unsigned short), &id2, sizeof(unsigned short)); +} + +void GLModel::Geometry::add_uint_line(unsigned int id1, unsigned int id2) +{ + if (format.index_type != EIndexType::UINT) { + assert(false); + return; + } + indices.resize(indices.size() + 2 * sizeof(unsigned int)); + ::memcpy(indices.data() + indices.size() - 2 * sizeof(unsigned int), &id1, sizeof(unsigned int)); + ::memcpy(indices.data() + indices.size() - sizeof(unsigned int), &id2, sizeof(unsigned int)); +} + +void GLModel::Geometry::add_ushort_triangle(unsigned short id1, unsigned short id2, unsigned short id3) +{ + if (format.index_type != EIndexType::USHORT) { + assert(false); + return; + } + indices.resize(indices.size() + 3 * sizeof(unsigned short)); + ::memcpy(indices.data() + indices.size() - 3 * sizeof(unsigned short), &id1, sizeof(unsigned short)); + ::memcpy(indices.data() + indices.size() - 2 * sizeof(unsigned short), &id2, sizeof(unsigned short)); + ::memcpy(indices.data() + indices.size() - sizeof(unsigned short), &id3, sizeof(unsigned short)); +} + +void GLModel::Geometry::add_uint_triangle(unsigned int id1, unsigned int id2, unsigned int id3) +{ + if (format.index_type != EIndexType::UINT) { + assert(false); + return; + } + indices.resize(indices.size() + 3 * sizeof(unsigned int)); + ::memcpy(indices.data() + indices.size() - 3 * sizeof(unsigned int), &id1, sizeof(unsigned int)); + ::memcpy(indices.data() + indices.size() - 2 * sizeof(unsigned int), &id2, sizeof(unsigned int)); + ::memcpy(indices.data() + indices.size() - sizeof(unsigned int), &id3, sizeof(unsigned int)); +} + +Vec2f GLModel::Geometry::extract_position_2(size_t id) const +{ + const size_t p_stride = position_stride_floats(format); + if (p_stride != 2) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + position_offset_floats(format)]; + return { *(start + 0), *(start + 1) }; +} + +Vec3f GLModel::Geometry::extract_position_3(size_t id) const +{ + const size_t p_stride = position_stride_floats(format); + if (p_stride != 3) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + position_offset_floats(format)]; + return { *(start + 0), *(start + 1), *(start + 2) }; +} + +Vec3f GLModel::Geometry::extract_normal_3(size_t id) const +{ + const size_t n_stride = normal_stride_floats(format); + if (n_stride != 3) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + normal_offset_floats(format)]; + return { *(start + 0), *(start + 1), *(start + 2) }; +} + +Vec2f GLModel::Geometry::extract_tex_coord_2(size_t id) const +{ + const size_t t_stride = tex_coord_stride_floats(format); + if (t_stride != 2) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + tex_coord_offset_floats(format)]; + return { *(start + 0), *(start + 1) }; +} + +unsigned int GLModel::Geometry::extract_uint_index(size_t id) const +{ + if (format.index_type != EIndexType::UINT) { + assert(false); + return -1; + } + + if (indices_count() <= id) { + assert(false); + return -1; + } + + unsigned int ret = -1; + ::memcpy(&ret, indices.data() + id * index_stride_bytes(format), sizeof(unsigned int)); + return ret; +} + +unsigned short GLModel::Geometry::extract_ushort_index(size_t id) const +{ + if (format.index_type != EIndexType::USHORT) { + assert(false); + return -1; + } + + if (indices_count() <= id) { + assert(false); + return -1; + } + + unsigned short ret = -1; + ::memcpy(&ret, indices.data() + id * index_stride_bytes(format), sizeof(unsigned short)); + return ret; +} + +size_t GLModel::Geometry::vertex_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: { return 2; } + case EVertexLayout::P2T2: { return 4; } + case EVertexLayout::P3: { return 3; } + case EVertexLayout::P3N3: { return 6; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::position_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: { return 2; } + case EVertexLayout::P3: + case EVertexLayout::P3N3: { return 3; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::position_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3N3: { return 0; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::normal_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P3N3: { return 3; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::normal_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P3N3: { return 3; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::tex_coord_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: { return 2; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::tex_coord_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: { return 2; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::index_stride_bytes(const Format& format) +{ + switch (format.index_type) + { + case EIndexType::UINT: { return sizeof(unsigned int); } + case EIndexType::USHORT: { return sizeof(unsigned short); } + default: { assert(false); return 0; } + }; +} + +bool GLModel::Geometry::has_position(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3N3: { return true; } + default: { assert(false); return false; } + }; +} + +bool GLModel::Geometry::has_normal(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: { return false; } + case EVertexLayout::P3N3: { return true; } + default: { assert(false); return false; } + }; +} + +bool GLModel::Geometry::has_tex_coord(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: { return true; } + case EVertexLayout::P2: + case EVertexLayout::P3: + case EVertexLayout::P3N3: { return false; } + default: { assert(false); return false; } + }; +} +#else +size_t GLModel::Geometry::vertices_count() const { size_t ret = 0; for (const Entity& entity : entities) { @@ -26,7 +348,7 @@ size_t GLModel::InitializationData::vertices_count() const return ret; } -size_t GLModel::InitializationData::indices_count() const +size_t GLModel::Geometry::indices_count() const { size_t ret = 0; for (const Entity& entity : entities) { @@ -34,13 +356,43 @@ size_t GLModel::InitializationData::indices_count() const } return ret; } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL -void GLModel::init_from(const InitializationData& data) +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLModel::init_from(Geometry&& data) +#else +void GLModel::init_from(const Geometry& data) +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + if (data.vertices.empty() || data.indices.empty()) { + assert(false); + return; + } + + m_render_data.geometry = std::move(data); + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + const size_t position_stride = Geometry::position_stride_floats(data.format); + if (position_stride == 3) + m_bounding_box.merge(m_render_data.geometry.extract_position_3(i).cast()); + else if (position_stride == 2) { + const Vec2f position = m_render_data.geometry.extract_position_2(i); + m_bounding_box.merge(Vec3f(position.x(), position.y(), 0.0f).cast()); + } + } +#else if (!m_render_data.empty()) // call reset() if you want to reuse this model return; - for (const InitializationData::Entity& entity : data.entities) { + for (const Geometry::Entity& entity : data.entities) { if (entity.positions.empty() || entity.indices.empty()) continue; @@ -72,15 +424,60 @@ void GLModel::init_from(const InitializationData& data) send_to_gpu(rdata, vertices, indices); m_render_data.emplace_back(rdata); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } -void GLModel::init_from(const indexed_triangle_set& its, const BoundingBoxf3 &bbox) +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLModel::init_from(const TriangleMesh& mesh) { + init_from(mesh.its); +} + +void GLModel::init_from(const indexed_triangle_set& its) +#else +void GLModel::init_from(const indexed_triangle_set& its, const BoundingBoxf3 &bbox) +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL +{ +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + if (its.vertices.empty() || its.indices.empty()){ + assert(false); + return; + } + + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3, Geometry::EIndexType::UINT }; + data.vertices.reserve(3 * its.indices.size() * Geometry::vertex_stride_floats(data.format)); + data.indices.reserve(3 * its.indices.size() * Geometry::index_stride_bytes(data.format)); + + // vertices + indices + unsigned int vertices_counter = 0; + for (uint32_t i = 0; i < its.indices.size(); ++i) { + stl_triangle_vertex_indices face = its.indices[i]; + stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; + stl_vertex n = face_normal_normalized(vertex); + for (size_t j = 0; j < 3; ++j) { + data.add_vertex(vertex[j], n); + } + vertices_counter += 3; + data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(m_render_data.geometry.extract_position_3(i).cast()); + } +#else if (!m_render_data.empty()) // call reset() if you want to reuse this model return; RenderData data; - data.type = PrimitiveType::Triangles; + data.type = EPrimitiveType::Triangles; std::vector vertices = std::vector(18 * its.indices.size()); std::vector indices = std::vector(3 * its.indices.size()); @@ -105,19 +502,63 @@ void GLModel::init_from(const indexed_triangle_set& its, const BoundingBoxf3 &bb send_to_gpu(data, vertices, indices); m_render_data.emplace_back(data); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } +#if !ENABLE_GLBEGIN_GLEND_REMOVAL void GLModel::init_from(const indexed_triangle_set& its) { - this->init_from(its, bounding_box(its)); + init_from(its, bounding_box(its)); } +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL void GLModel::init_from(const Polygons& polygons, float z) { - auto append_polygon = [](const Polygon& polygon, float z, GUI::GLModel::InitializationData& data) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + if (polygons.empty()) { + assert(false); + return; + } + + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Lines, Geometry::EVertexLayout::P3, Geometry::EIndexType::UINT }; + + size_t segments_count = 0; + for (const Polygon& polygon : polygons) { + segments_count += polygon.points.size(); + } + + data.vertices.reserve(2 * segments_count * Geometry::vertex_stride_floats(data.format)); + data.indices.reserve(2 * segments_count * Geometry::index_stride_bytes(data.format)); + + // vertices + indices + unsigned int vertices_counter = 0; + for (const Polygon& poly : polygons) { + for (size_t i = 0; i < poly.points.size(); ++i) { + const Point& p0 = poly.points[i]; + const Point& p1 = (i == poly.points.size() - 1) ? poly.points.front() : poly.points[i + 1]; + data.add_vertex(Vec3f(unscale(p0.x()), unscale(p0.y()), z)); + data.add_vertex(Vec3f(unscale(p1.x()), unscale(p1.y()), z)); + vertices_counter += 2; + data.add_uint_line(vertices_counter - 2, vertices_counter - 1); + } + } + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(m_render_data.geometry.extract_position_3(i).cast()); + } +#else + auto append_polygon = [](const Polygon& polygon, float z, GUI::GLModel::Geometry& data) { if (!polygon.empty()) { - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::LineLoop; + GUI::GLModel::Geometry::Entity entity; + entity.type = GUI::GLModel::EPrimitiveType::LineLoop; // contour entity.positions.reserve(polygon.size() + 1); entity.indices.reserve(polygon.size() + 1); @@ -132,11 +573,12 @@ void GLModel::init_from(const Polygons& polygons, float z) } }; - InitializationData init_data; + Geometry init_data; for (const Polygon& polygon : polygons) { append_polygon(polygon, z, init_data); } init_from(init_data); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } bool GLModel::init_from_file(const std::string& filename) @@ -148,24 +590,27 @@ bool GLModel::init_from_file(const std::string& filename) return false; Model model; - try - { + try { model = Model::read_from_file(filename); } - catch (std::exception&) - { + catch (std::exception&) { return false; } - TriangleMesh mesh = model.mesh(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + init_from(model.mesh()); +#else + const TriangleMesh& mesh = model.mesh(); init_from(mesh.its, mesh.bounding_box()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL m_filename = filename; return true; } -void GLModel::set_color(int entity_id, const std::array& color) +#if !ENABLE_GLBEGIN_GLEND_REMOVAL +void GLModel::set_color(int entity_id, const ColorRGBA& color) { for (size_t i = 0; i < m_render_data.size(); ++i) { if (entity_id == -1 || static_cast(i) == entity_id) @@ -173,8 +618,31 @@ void GLModel::set_color(int entity_id, const std::array& color) } } +ColorRGBA GLModel::get_color(size_t entity_id) const +{ + if (entity_id < 0 || entity_id >= m_render_data.size()) return ColorRGBA{}; + return m_render_data[entity_id].color; +} +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL + void GLModel::reset() { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // release gpu memory + if (m_render_data.ibo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_render_data.ibo_id)); + m_render_data.ibo_id = 0; + } + if (m_render_data.vbo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_render_data.vbo_id)); + m_render_data.vbo_id = 0; + } + + m_render_data.vertices_count = 0; + m_render_data.indices_count = 0; + m_render_data.geometry.vertices = std::vector(); + m_render_data.geometry.indices = std::vector(); +#else for (RenderData& data : m_render_data) { // release gpu memory if (data.ibo_id > 0) @@ -184,14 +652,94 @@ void GLModel::reset() } m_render_data.clear(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL m_bounding_box = BoundingBoxf3(); m_filename = std::string(); } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +static GLenum get_primitive_mode(const GLModel::Geometry::Format& format) +{ + switch (format.type) + { + case GLModel::Geometry::EPrimitiveType::Points: { return GL_POINTS; } + default: + case GLModel::Geometry::EPrimitiveType::Triangles: { return GL_TRIANGLES; } + case GLModel::Geometry::EPrimitiveType::TriangleStrip: { return GL_TRIANGLE_STRIP; } + case GLModel::Geometry::EPrimitiveType::TriangleFan: { return GL_TRIANGLE_FAN; } + case GLModel::Geometry::EPrimitiveType::Lines: { return GL_LINES; } + case GLModel::Geometry::EPrimitiveType::LineStrip: { return GL_LINE_STRIP; } + case GLModel::Geometry::EPrimitiveType::LineLoop: { return GL_LINE_LOOP; } + } +} + +static GLenum get_index_type(const GLModel::Geometry::Format& format) +{ + switch (format.index_type) + { + default: + case GLModel::Geometry::EIndexType::UINT: { return GL_UNSIGNED_INT; } + case GLModel::Geometry::EIndexType::USHORT: { return GL_UNSIGNED_SHORT; } + } +} + +void GLModel::render() +#else void GLModel::render() const +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL { GLShaderProgram* shader = wxGetApp().get_current_shader(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (shader == nullptr) + return; + + // sends data to gpu if not done yet + if (m_render_data.vbo_id == 0 || m_render_data.ibo_id == 0) { + if (m_render_data.geometry.vertices_count() > 0 && m_render_data.geometry.indices_count() > 0 && !send_to_gpu()) + return; + } + + const Geometry& data = m_render_data.geometry; + + GLenum mode = get_primitive_mode(data.format); + GLenum index_type = get_index_type(data.format); + + const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); + const bool position = Geometry::has_position(data.format); + const bool normal = Geometry::has_normal(data.format); + const bool tex_coord = Geometry::has_tex_coord(data.format); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + + if (position) { + glsafe(::glVertexPointer(Geometry::position_stride_floats(data.format), GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::position_offset_bytes(data.format))); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + } + if (normal) { + glsafe(::glNormalPointer(GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::normal_offset_bytes(data.format))); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + } + if (tex_coord) { + glsafe(::glTexCoordPointer(Geometry::tex_coord_stride_floats(data.format), GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::tex_coord_offset_bytes(data.format))); + glsafe(::glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + } + + shader->set_uniform("uniform_color", data.color); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + glsafe(::glDrawElements(mode, indices_count(), index_type, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (tex_coord) + glsafe(::glDisableClientState(GL_TEXTURE_COORD_ARRAY)); + if (normal) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + if (position) + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +#else for (const RenderData& data : m_render_data) { if (data.vbo_id == 0 || data.ibo_id == 0) continue; @@ -200,10 +748,10 @@ void GLModel::render() const switch (data.type) { default: - case PrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } - case PrimitiveType::Lines: { mode = GL_LINES; break; } - case PrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } - case PrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } + case EPrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } + case EPrimitiveType::Lines: { mode = GL_LINES; break; } + case EPrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } + case EPrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } } glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id)); @@ -227,14 +775,40 @@ void GLModel::render() const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) +#else void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) const +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL { if (instances_vbo == 0) return; GLShaderProgram* shader = wxGetApp().get_current_shader(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (shader == nullptr || !boost::algorithm::iends_with(shader->get_name(), "_instanced")) + return; + + // vertex attributes + GLint position_id = shader->get_attrib_location("v_position"); + GLint normal_id = shader->get_attrib_location("v_normal"); + if (position_id == -1 || normal_id == -1) + return; + + // instance attributes + GLint offset_id = shader->get_attrib_location("i_offset"); + GLint scales_id = shader->get_attrib_location("i_scales"); + if (offset_id == -1 || scales_id == -1) + return; + + if (m_render_data.vbo_id == 0 || m_render_data.ibo_id == 0) { + if (!send_to_gpu()) + return; + } +#else assert(shader == nullptr || boost::algorithm::iends_with(shader->get_name(), "_instanced")); // vertex attributes @@ -246,8 +820,18 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance GLint offset_id = (shader != nullptr) ? shader->get_attrib_location("i_offset") : -1; GLint scales_id = (shader != nullptr) ? shader->get_attrib_location("i_scales") : -1; assert(offset_id != -1 && scales_id != -1); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glBindBuffer(GL_ARRAY_BUFFER, instances_vbo)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + glsafe(::glVertexAttribPointer(offset_id, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)0)); + glsafe(::glEnableVertexAttribArray(offset_id)); + glsafe(::glVertexAttribDivisor(offset_id, 1)); + + glsafe(::glVertexAttribPointer(scales_id, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)(3 * sizeof(float)))); + glsafe(::glEnableVertexAttribArray(scales_id)); + glsafe(::glVertexAttribDivisor(scales_id, 1)); +#else if (offset_id != -1) { glsafe(::glVertexAttribPointer(offset_id, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)0)); glsafe(::glEnableVertexAttribArray(offset_id)); @@ -258,7 +842,44 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance glsafe(::glEnableVertexAttribArray(scales_id)); glsafe(::glVertexAttribDivisor(scales_id, 1)); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_GLBEGIN_GLEND_REMOVAL + const Geometry& data = m_render_data.geometry; + + GLenum mode = get_primitive_mode(data.format); + GLenum index_type = get_index_type(data.format); + + shader->set_uniform("uniform_color", data.color); + + const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); + const bool position = Geometry::has_position(data.format); + const bool normal = Geometry::has_normal(data.format); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + + if (position) { + glsafe(::glVertexAttribPointer(position_id, Geometry::position_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (GLvoid*)Geometry::position_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(position_id)); + } + + if (normal) { + glsafe(::glVertexAttribPointer(normal_id, Geometry::normal_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (GLvoid*)Geometry::normal_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(normal_id)); + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + glsafe(::glDrawElementsInstanced(mode, indices_count(), index_type, (const void*)0, instances_count)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (normal) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position) + glsafe(::glDisableVertexAttribArray(position_id)); + + glsafe(::glDisableVertexAttribArray(scales_id)); + glsafe(::glDisableVertexAttribArray(offset_id)); +#else for (const RenderData& data : m_render_data) { if (data.vbo_id == 0 || data.ibo_id == 0) continue; @@ -267,10 +888,10 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance switch (data.type) { default: - case PrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } - case PrimitiveType::Lines: { mode = GL_LINES; break; } - case PrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } - case PrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } + case EPrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } + case EPrimitiveType::Lines: { mode = GL_LINES; break; } + case EPrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } + case EPrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } } if (shader != nullptr) @@ -302,10 +923,44 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance glsafe(::glDisableVertexAttribArray(scales_id)); if (offset_id != -1) glsafe(::glDisableVertexAttribArray(offset_id)); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +bool GLModel::send_to_gpu() +{ + if (m_render_data.vbo_id > 0 || m_render_data.ibo_id > 0) { + assert(false); + return false; + } + + Geometry& data = m_render_data.geometry; + if (data.vertices.empty() || data.indices.empty()) { + assert(false); + return false; + } + + // vertices + glsafe(::glGenBuffers(1, &m_render_data.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, data.vertices_size_bytes(), data.vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + m_render_data.vertices_count = vertices_count(); + data.vertices = std::vector(); + + // indices + glsafe(::glGenBuffers(1, &m_render_data.ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indices_size_bytes(), data.indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + m_render_data.indices_count = indices_count(); + data.indices = std::vector(); + + return true; +} +#else void GLModel::send_to_gpu(RenderData& data, const std::vector& vertices, const std::vector& indices) { assert(data.vbo_id == 0); @@ -323,31 +978,57 @@ void GLModel::send_to_gpu(RenderData& data, const std::vector& vertices, glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL -GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) +#if ENABLE_GLBEGIN_GLEND_REMOVAL +static void append_vertex(GLModel::Geometry& data, const Vec3f& position, const Vec3f& normal) { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { + data.add_vertex(position, normal); +} + +static void append_triangle(GLModel::Geometry& data, unsigned short v1, unsigned short v2, unsigned short v3) +{ + data.add_ushort_index(v1); + data.add_ushort_index(v2); + data.add_ushort_index(v3); +} +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + +GLModel::Geometry stilized_arrow(unsigned short resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) +{ +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + auto append_vertex = [](GLModel::Geometry::Entity& entity, const Vec3f& position, const Vec3f& normal) { entity.positions.emplace_back(position); entity.normals.emplace_back(normal); }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { + auto append_indices = [](GLModel::Geometry::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { entity.indices.emplace_back(v1); entity.indices.emplace_back(v2); entity.indices.emplace_back(v3); }; +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL - resolution = std::max(4, resolution); + resolution = std::max(4, resolution); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + resolution = std::min(10922, resolution); // ensure no unsigned short overflow of indices +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; + data.vertices.reserve((6 * resolution + 2) * GLModel::Geometry::vertex_stride_floats(data.format)); + data.indices.reserve((6 * resolution * 3) * GLModel::Geometry::index_stride_bytes(data.format)); +#else + GLModel::Geometry::Entity entity; + entity.type = GLModel::EPrimitiveType::Triangles; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - const float angle_step = 2.0f * M_PI / static_cast(resolution); + const float angle_step = 2.0f * float(PI) / float(resolution); std::vector cosines(resolution); std::vector sines(resolution); - for (int i = 0; i < resolution; ++i) { - const float angle = angle_step * static_cast(i); + for (unsigned short i = 0; i < resolution; ++i) { + const float angle = angle_step * float(i); cosines[i] = ::cos(angle); sines[i] = -::sin(angle); } @@ -355,6 +1036,66 @@ GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, flo const float total_height = tip_height + stem_height; // tip vertices/normals +#if ENABLE_GLBEGIN_GLEND_REMOVAL + append_vertex(data, { 0.0f, 0.0f, total_height }, Vec3f::UnitZ()); + for (unsigned short i = 0; i < resolution; ++i) { + append_vertex(data, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); + } + + // tip triangles + for (unsigned short i = 0; i < resolution; ++i) { + const unsigned short v3 = (i < resolution - 1) ? i + 2 : 1; + append_triangle(data, 0, i + 1, v3); + } + + // tip cap outer perimeter vertices + for (unsigned short i = 0; i < resolution; ++i) { + append_vertex(data, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, -Vec3f::UnitZ()); + } + + // tip cap inner perimeter vertices + for (unsigned short i = 0; i < resolution; ++i) { + append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, -Vec3f::UnitZ()); + } + + // tip cap triangles + for (unsigned short i = 0; i < resolution; ++i) { + const unsigned short v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1; + const unsigned short v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1; + append_triangle(data, i + resolution + 1, v3, v2); + append_triangle(data, i + resolution + 1, i + 2 * resolution + 1, v3); + } + + // stem bottom vertices + for (unsigned short i = 0; i < resolution; ++i) { + append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); + } + + // stem top vertices + for (unsigned short i = 0; i < resolution; ++i) { + append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, { sines[i], cosines[i], 0.0f }); + } + + // stem triangles + for (unsigned short i = 0; i < resolution; ++i) { + const unsigned short v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1; + const unsigned short v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1; + append_triangle(data, i + 3 * resolution + 1, v3, v2); + append_triangle(data, i + 3 * resolution + 1, i + 4 * resolution + 1, v3); + } + + // stem cap vertices + append_vertex(data, Vec3f::Zero(), -Vec3f::UnitZ()); + for (unsigned short i = 0; i < resolution; ++i) { + append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, -Vec3f::UnitZ()); + } + + // stem cap triangles + for (unsigned short i = 0; i < resolution; ++i) { + const unsigned short v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2; + append_triangle(data, 5 * resolution + 1, v3, i + 5 * resolution + 2); + } +#else append_vertex(entity, { 0.0f, 0.0f, total_height }, Vec3f::UnitZ()); for (int i = 0; i < resolution; ++i) { append_vertex(entity, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); @@ -415,26 +1156,38 @@ GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, flo } data.entities.emplace_back(entity); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL return data; } -GLModel::InitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness) +GLModel::Geometry circular_arrow(unsigned short resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness) { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + auto append_vertex = [](GLModel::Geometry::Entity& entity, const Vec3f& position, const Vec3f& normal) { entity.positions.emplace_back(position); entity.normals.emplace_back(normal); }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { + auto append_indices = [](GLModel::Geometry::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { entity.indices.emplace_back(v1); entity.indices.emplace_back(v2); entity.indices.emplace_back(v3); }; +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL - resolution = std::max(2, resolution); + resolution = std::max(2, resolution); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + resolution = std::min(8188, resolution); // ensure no unsigned short overflow of indices +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; + data.vertices.reserve((8 * (resolution + 1) + 30) * GLModel::Geometry::vertex_stride_floats(data.format)); + data.indices.reserve(((8 * resolution + 16) * 3) * GLModel::Geometry::index_stride_bytes(data.format)); +#else + GLModel::Geometry::Entity entity; + entity.type = GLModel::EPrimitiveType::Triangles; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL const float half_thickness = 0.5f * thickness; const float half_stem_width = 0.5f * stem_width; @@ -442,8 +1195,153 @@ GLModel::InitializationData circular_arrow(int resolution, float radius, float t const float outer_radius = radius + half_stem_width; const float inner_radius = radius - half_stem_width; - const float step_angle = 0.5f * PI / static_cast(resolution); + const float step_angle = 0.5f * float(PI) / float(resolution); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // tip + // top face vertices + append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { -tip_height, radius, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitZ()); + + // top face triangles + append_triangle(data, 0, 1, 2); + append_triangle(data, 0, 2, 4); + append_triangle(data, 4, 2, 3); + + // bottom face vertices + append_vertex(data, { 0.0f, outer_radius, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { -tip_height, radius, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { 0.0f, inner_radius, -half_thickness }, -Vec3f::UnitZ()); + + // bottom face triangles + append_triangle(data, 5, 7, 6); + append_triangle(data, 5, 9, 7); + append_triangle(data, 9, 8, 7); + + // side faces vertices + append_vertex(data, { 0.0f, outer_radius, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitX()); + + Vec3f normal(-half_tip_width, tip_height, 0.0f); + normal.normalize(); + append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, normal); + append_vertex(data, { -tip_height, radius, -half_thickness }, normal); + append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, normal); + append_vertex(data, { -tip_height, radius, half_thickness }, normal); + + normal = { -half_tip_width, -tip_height, 0.0f }; + normal.normalize(); + append_vertex(data, { -tip_height, radius, -half_thickness }, normal); + append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, normal); + append_vertex(data, { -tip_height, radius, half_thickness }, normal); + append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, normal); + + append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, inner_radius, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitX()); + + // side face triangles + for (unsigned short i = 0; i < 4; ++i) { + const unsigned short ii = i * 4; + append_triangle(data, 10 + ii, 11 + ii, 13 + ii); + append_triangle(data, 10 + ii, 13 + ii, 12 + ii); + } + + // stem + // top face vertices + for (unsigned short i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + append_vertex(data, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ()); + } + + for (unsigned short i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + append_vertex(data, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ()); + } + + // top face triangles + for (unsigned short i = 0; i < resolution; ++i) { + append_triangle(data, 26 + i, 27 + i, 27 + resolution + i); + append_triangle(data, 27 + i, 28 + resolution + i, 27 + resolution + i); + } + + // bottom face vertices + for (unsigned short i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + append_vertex(data, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ()); + } + + for (unsigned short i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + append_vertex(data, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ()); + } + + // bottom face triangles + for (unsigned short i = 0; i < resolution; ++i) { + append_triangle(data, 28 + 2 * resolution + i, 29 + 3 * resolution + i, 29 + 2 * resolution + i); + append_triangle(data, 29 + 2 * resolution + i, 29 + 3 * resolution + i, 30 + 3 * resolution + i); + } + + // side faces vertices and triangles + for (unsigned short i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + const float c = ::cos(angle); + const float s = ::sin(angle); + append_vertex(data, { inner_radius * s, inner_radius * c, -half_thickness }, { -s, -c, 0.0f }); + } + + for (unsigned short i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + const float c = ::cos(angle); + const float s = ::sin(angle); + append_vertex(data, { inner_radius * s, inner_radius * c, half_thickness }, { -s, -c, 0.0f }); + } + + unsigned short first_id = 26 + 4 * (resolution + 1); + for (unsigned short i = 0; i < resolution; ++i) { + const unsigned short ii = first_id + i; + append_triangle(data, ii, ii + 1, ii + resolution + 2); + append_triangle(data, ii, ii + resolution + 2, ii + resolution + 1); + } + + append_vertex(data, { inner_radius, 0.0f, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { outer_radius, 0.0f, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { inner_radius, 0.0f, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { outer_radius, 0.0f, half_thickness }, -Vec3f::UnitY()); + + first_id = 26 + 6 * (resolution + 1); + append_triangle(data, first_id, first_id + 1, first_id + 3); + append_triangle(data, first_id, first_id + 3, first_id + 2); + + for (short i = resolution; i >= 0; --i) { + const float angle = float(i) * step_angle; + const float c = ::cos(angle); + const float s = ::sin(angle); + append_vertex(data, { outer_radius * s, outer_radius * c, -half_thickness }, { s, c, 0.0f }); + } + + for (short i = resolution; i >= 0; --i) { + const float angle = float(i) * step_angle; + const float c = ::cos(angle); + const float s = ::sin(angle); + append_vertex(data, { outer_radius * s, outer_radius * c, +half_thickness }, { s, c, 0.0f }); + } + + first_id = 30 + 6 * (resolution + 1); + for (unsigned short i = 0; i < resolution; ++i) { + const unsigned short ii = first_id + i; + append_triangle(data, ii, ii + 1, ii + resolution + 2); + append_triangle(data, ii, ii + resolution + 2, ii + resolution + 1); + } +#else // tip // top face vertices append_vertex(entity, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitZ()); @@ -589,30 +1487,119 @@ GLModel::InitializationData circular_arrow(int resolution, float radius, float t } data.entities.emplace_back(entity); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL return data; } -GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness) +GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness) { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + auto append_vertex = [](GLModel::Geometry::Entity& entity, const Vec3f& position, const Vec3f& normal) { entity.positions.emplace_back(position); entity.normals.emplace_back(normal); }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { + auto append_indices = [](GLModel::Geometry::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { entity.indices.emplace_back(v1); entity.indices.emplace_back(v2); entity.indices.emplace_back(v3); }; +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; + data.vertices.reserve(42 * GLModel::Geometry::vertex_stride_floats(data.format)); + data.indices.reserve((24 * 3) * GLModel::Geometry::index_stride_bytes(data.format)); +#else + GLModel::Geometry::Entity entity; + entity.type = GLModel::EPrimitiveType::Triangles; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL const float half_thickness = 0.5f * thickness; const float half_stem_width = 0.5f * stem_width; const float half_tip_width = 0.5f * tip_width; const float total_height = tip_height + stem_height; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // top face vertices + append_vertex(data, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { 0.0, total_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { -half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { -half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { -half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); + + // top face triangles + append_triangle(data, 0, 1, 6); + append_triangle(data, 6, 1, 5); + append_triangle(data, 4, 5, 3); + append_triangle(data, 5, 1, 3); + append_triangle(data, 1, 2, 3); + + // bottom face vertices + append_vertex(data, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { 0.0, total_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ()); + + // bottom face triangles + append_triangle(data, 7, 13, 8); + append_triangle(data, 13, 12, 8); + append_triangle(data, 12, 11, 10); + append_triangle(data, 8, 12, 10); + append_triangle(data, 9, 8, 10); + + // side faces vertices + append_vertex(data, { half_stem_width, 0.0, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { half_stem_width, stem_height, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitX()); + append_vertex(data, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitX()); + + append_vertex(data, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY()); + + Vec3f normal(tip_height, half_tip_width, 0.0f); + normal.normalize(); + append_vertex(data, { half_tip_width, stem_height, -half_thickness }, normal); + append_vertex(data, { 0.0, total_height, -half_thickness }, normal); + append_vertex(data, { half_tip_width, stem_height, half_thickness }, normal); + append_vertex(data, { 0.0, total_height, half_thickness }, normal); + + normal = { -tip_height, half_tip_width, 0.0f }; + normal.normalize(); + append_vertex(data, { 0.0, total_height, -half_thickness }, normal); + append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, normal); + append_vertex(data, { 0.0, total_height, half_thickness }, normal); + append_vertex(data, { -half_tip_width, stem_height, half_thickness }, normal); + + append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { -half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY()); + + append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitX()); + append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitX()); + append_vertex(data, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitX()); + append_vertex(data, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitX()); + + append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY()); + + // side face triangles + for (unsigned short i = 0; i < 7; ++i) { + const unsigned short ii = i * 4; + append_triangle(data, 14 + ii, 15 + ii, 17 + ii); + append_triangle(data, 14 + ii, 17 + ii, 16 + ii); + } +#else // top face vertices append_vertex(entity, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); append_vertex(entity, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); @@ -693,19 +1680,54 @@ GLModel::InitializationData straight_arrow(float tip_width, float tip_height, fl } data.entities.emplace_back(entity); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL return data; } -GLModel::InitializationData diamond(int resolution) +GLModel::Geometry diamond(unsigned short resolution) { - resolution = std::max(4, resolution); + resolution = std::max(4, resolution); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + resolution = std::min(65534, resolution); // ensure no unsigned short overflow of indices +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; + data.vertices.reserve((resolution + 2) * GLModel::Geometry::vertex_stride_floats(data.format)); + data.indices.reserve(((2 * (resolution + 1)) * 3) * GLModel::Geometry::index_stride_bytes(data.format)); +#else + GLModel::Geometry::Entity entity; + entity.type = GLModel::EPrimitiveType::Triangles; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL const float step = 2.0f * float(PI) / float(resolution); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // vertices + for (unsigned short i = 0; i < resolution; ++i) { + float ii = float(i) * step; + const Vec3f p = { 0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f }; + append_vertex(data, p, p.normalized()); + } + Vec3f p = { 0.0f, 0.0f, 0.5f }; + append_vertex(data, p, p.normalized()); + p = { 0.0f, 0.0f, -0.5f }; + append_vertex(data, p, p.normalized()); + + // triangles + // top + for (unsigned short i = 0; i < resolution; ++i) { + append_triangle(data, i + 0, i + 1, resolution); + } + append_triangle(data, resolution - 1, 0, resolution); + + // bottom + for (unsigned short i = 0; i < resolution; ++i) { + append_triangle(data, i + 0, resolution + 1, i + 1); + } + append_triangle(data, resolution - 1, resolution + 1, 0); +#else // positions for (int i = 0; i < resolution; ++i) { float ii = float(i) * step; @@ -741,6 +1763,7 @@ GLModel::InitializationData diamond(int resolution) entity.indices.push_back(0); data.entities.emplace_back(entity); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL return data; } diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index d47c56fd93..61456f3773 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -3,6 +3,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Color.hpp" #include #include @@ -19,7 +20,8 @@ namespace GUI { class GLModel { public: - enum class PrimitiveType : unsigned char +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + enum class EPrimitiveType : unsigned char { Triangles, Lines, @@ -29,22 +31,114 @@ namespace GUI { struct RenderData { - PrimitiveType type; + EPrimitiveType type; unsigned int vbo_id{ 0 }; unsigned int ibo_id{ 0 }; size_t indices_count{ 0 }; - std::array color{ 1.0f, 1.0f, 1.0f, 1.0f }; + ColorRGBA color; }; +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL - struct InitializationData + struct Geometry { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + enum class EPrimitiveType : unsigned char + { + Points, + Triangles, + TriangleStrip, + TriangleFan, + Lines, + LineStrip, + LineLoop + }; + + enum class EVertexLayout : unsigned char + { + P2, // position 2 floats + P2T2, // position 2 floats + texture coords 2 floats + P3, // position 3 floats + P3N3, // position 3 floats + normal 3 floats + }; + + enum class EIndexType : unsigned char + { + UINT, // unsigned int + USHORT // unsigned short + }; + + struct Format + { + EPrimitiveType type{ EPrimitiveType::Triangles }; + EVertexLayout vertex_layout{ EVertexLayout::P3N3 }; + EIndexType index_type{ EIndexType::UINT }; + }; + + Format format; + std::vector vertices; + std::vector indices; + ColorRGBA color{ ColorRGBA::BLACK() }; + + void add_vertex(const Vec2f& position); + void add_vertex(const Vec2f& position, const Vec2f& tex_coord); + void add_vertex(const Vec3f& position); + void add_vertex(const Vec3f& position, const Vec3f& normal); + + void add_ushort_index(unsigned short id); + void add_uint_index(unsigned int id); + + void add_ushort_line(unsigned short id1, unsigned short id2); + void add_uint_line(unsigned int id1, unsigned int id2); + + void add_ushort_triangle(unsigned short id1, unsigned short id2, unsigned short id3); + void add_uint_triangle(unsigned int id1, unsigned int id2, unsigned int id3); + + Vec2f extract_position_2(size_t id) const; + Vec3f extract_position_3(size_t id) const; + Vec3f extract_normal_3(size_t id) const; + Vec2f extract_tex_coord_2(size_t id) const; + + unsigned int extract_uint_index(size_t id) const; + unsigned short extract_ushort_index(size_t id) const; + + size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); } + size_t indices_count() const { return indices.size() / index_stride_bytes(format); } + + size_t vertices_size_floats() const { return vertices.size(); } + size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } + size_t indices_size_bytes() const { return indices.size(); } + + static size_t vertex_stride_floats(const Format& format); + static size_t vertex_stride_bytes(const Format& format) { return vertex_stride_floats(format) * sizeof(float); } + + static size_t position_stride_floats(const Format& format); + static size_t position_stride_bytes(const Format& format) { return position_stride_floats(format) * sizeof(float); } + static size_t position_offset_floats(const Format& format); + static size_t position_offset_bytes(const Format& format) { return position_offset_floats(format) * sizeof(float); } + + static size_t normal_stride_floats(const Format& format); + static size_t normal_stride_bytes(const Format& format) { return normal_stride_floats(format) * sizeof(float); } + static size_t normal_offset_floats(const Format& format); + static size_t normal_offset_bytes(const Format& format) { return normal_offset_floats(format) * sizeof(float); } + + static size_t tex_coord_stride_floats(const Format& format); + static size_t tex_coord_stride_bytes(const Format& format) { return tex_coord_stride_floats(format) * sizeof(float); } + static size_t tex_coord_offset_floats(const Format& format); + static size_t tex_coord_offset_bytes(const Format& format) { return tex_coord_offset_floats(format) * sizeof(float); } + + static size_t index_stride_bytes(const Format& format); + + static bool has_position(const Format& format); + static bool has_normal(const Format& format); + static bool has_tex_coord(const Format& format); +#else struct Entity { - PrimitiveType type; + EPrimitiveType type; std::vector positions; std::vector normals; std::vector indices; - std::array color{ 1.0f, 1.0f, 1.0f, 1.0f }; + ColorRGBA color; }; std::vector entities; @@ -55,10 +149,26 @@ namespace GUI { size_t indices_count() const; size_t indices_size_bytes() const { return indices_count() * sizeof(unsigned int); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL }; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + struct RenderData + { + Geometry geometry; + unsigned int vbo_id{ 0 }; + unsigned int ibo_id{ 0 }; + size_t vertices_count{ 0 }; + size_t indices_count{ 0 }; + }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + private: +#if ENABLE_GLBEGIN_GLEND_REMOVAL + RenderData m_render_data; +#else std::vector m_render_data; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL BoundingBoxf3 m_bounding_box; std::string m_filename; @@ -67,50 +177,82 @@ namespace GUI { GLModel() = default; virtual ~GLModel() { reset(); } - void init_from(const InitializationData& data); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + size_t vertices_count() const { return m_render_data.vertices_count > 0 ? + m_render_data.vertices_count : m_render_data.geometry.vertices_count(); } + size_t indices_count() const { return m_render_data.indices_count > 0 ? + m_render_data.indices_count : m_render_data.geometry.indices_count(); } + + size_t vertices_size_floats() const { return vertices_count() * Geometry::vertex_stride_floats(m_render_data.geometry.format); } + size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } + + size_t indices_size_bytes() const { return indices_count() * Geometry::index_stride_bytes(m_render_data.geometry.format); } + + void init_from(Geometry&& data); + void init_from(const TriangleMesh& mesh); +#else + void init_from(const Geometry& data); void init_from(const indexed_triangle_set& its, const BoundingBoxf3& bbox); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL void init_from(const indexed_triangle_set& its); void init_from(const Polygons& polygons, float z); bool init_from_file(const std::string& filename); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + void set_color(const ColorRGBA& color) { m_render_data.geometry.color = color; } + const ColorRGBA& get_color() const { return m_render_data.geometry.color; } +#else // if entity_id == -1 set the color of all entities - void set_color(int entity_id, const std::array& color); + void set_color(int entity_id, const ColorRGBA& color); + ColorRGBA get_color(size_t entity_id = 0U) const; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL void reset(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + void render(); + void render_instanced(unsigned int instances_vbo, unsigned int instances_count); + + bool is_initialized() const { return vertices_count() > 0 && indices_count() > 0; } +#else void render() const; void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const; bool is_initialized() const { return !m_render_data.empty(); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } const std::string& get_filename() const { return m_filename; } private: +#if ENABLE_GLBEGIN_GLEND_REMOVAL + bool send_to_gpu(); +#else void send_to_gpu(RenderData& data, const std::vector& vertices, const std::vector& indices); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL }; // create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution // the origin of the arrow is in the center of the stem cap // the arrow has its axis of symmetry along the Z axis and is pointing upward // used to render bed axes and sequential marker - GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); + GLModel::Geometry stilized_arrow(unsigned short resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); // create an arrow whose stem is a quarter of circle, with the given dimensions and resolution // the origin of the arrow is in the center of the circle // the arrow is contained in the 1st quadrant of the XY plane and is pointing counterclockwise // used to render sidebar hints for rotations - GLModel::InitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness); + GLModel::Geometry circular_arrow(unsigned short resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness); // create an arrow with the given dimensions // the origin of the arrow is in the center of the stem cap // the arrow is contained in XY plane and has its main axis along the Y axis // used to render sidebar hints for position and scale - GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); + GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); // create a diamond with the given resolution // the origin of the diamond is in its center // the diamond is contained into a box with size [1, 1, 1] - GLModel::InitializationData diamond(int resolution); + GLModel::Geometry diamond(unsigned short resolution); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 13467499a1..5331d517d8 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -69,7 +69,7 @@ namespace GUI { m_state = Off; } - void GLSelectionRectangle::render(const GLCanvas3D& canvas) const + void GLSelectionRectangle::render(const GLCanvas3D& canvas) { if (!is_dragging()) return; @@ -80,23 +80,25 @@ namespace GUI { Size cnv_size = canvas.get_canvas_size(); float cnv_half_width = 0.5f * (float)cnv_size.get_width(); float cnv_half_height = 0.5f * (float)cnv_size.get_height(); - if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) + if (cnv_half_width == 0.0f || cnv_half_height == 0.0f) return; Vec2d start(m_start_corner(0) - cnv_half_width, cnv_half_height - m_start_corner(1)); Vec2d end(m_end_corner(0) - cnv_half_width, cnv_half_height - m_end_corner(1)); - float left = (float)std::min(start(0), end(0)) * inv_zoom; - float top = (float)std::max(start(1), end(1)) * inv_zoom; - float right = (float)std::max(start(0), end(0)) * inv_zoom; - float bottom = (float)std::min(start(1), end(1)) * inv_zoom; - + const float left = (float)std::min(start(0), end(0)) * inv_zoom; + const float top = (float)std::max(start(1), end(1)) * inv_zoom; + const float right = (float)std::max(start(0), end(0)) * inv_zoom; + const float bottom = (float)std::min(start(1), end(1)) * inv_zoom; + glsafe(::glLineWidth(1.5f)); +#if !ENABLE_GLBEGIN_GLEND_REMOVAL float color[3]; color[0] = (m_state == Select) ? 0.3f : 1.0f; color[1] = (m_state == Select) ? 1.0f : 0.3f; color[2] = 0.3f; glsafe(::glColor3fv(color)); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glDisable(GL_DEPTH_TEST)); @@ -105,19 +107,59 @@ namespace GUI { // ensure that the rectangle is renderered inside the frustrum glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); // ensure that the overlay fits the frustrum near z plane - double gui_scale = camera.get_gui_scale(); + const double gui_scale = camera.get_gui_scale(); glsafe(::glScaled(gui_scale, gui_scale, 1.0)); glsafe(::glPushAttrib(GL_ENABLE_BIT)); glsafe(::glLineStipple(4, 0xAAAA)); glsafe(::glEnable(GL_LINE_STIPPLE)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + if (!m_rectangle.is_initialized() || !m_old_start_corner.isApprox(m_start_corner) || !m_old_end_corner.isApprox(m_end_corner)) { + m_old_start_corner = m_start_corner; + m_old_end_corner = m_end_corner; + m_rectangle.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(4 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex(Vec2f(left, bottom)); + init_data.add_vertex(Vec2f(right, bottom)); + init_data.add_vertex(Vec2f(right, top)); + init_data.add_vertex(Vec2f(left, top)); + + // indices + init_data.add_ushort_index(0); + init_data.add_ushort_index(1); + init_data.add_ushort_index(2); + init_data.add_ushort_index(3); + + m_rectangle.init_from(std::move(init_data)); + } + + const ColorRGBA color( + (m_state == Select) ? 0.3f : 1.0f, + (m_state == Select) ? 1.0f : 0.3f, + 0.3f, 1.0f); + m_rectangle.set_color(color); + m_rectangle.render(); + shader->stop_using(); + } +#else ::glBegin(GL_LINE_LOOP); ::glVertex2f((GLfloat)left, (GLfloat)bottom); ::glVertex2f((GLfloat)right, (GLfloat)bottom); ::glVertex2f((GLfloat)right, (GLfloat)top); ::glVertex2f((GLfloat)left, (GLfloat)top); glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glPopAttrib()); diff --git a/src/slic3r/GUI/GLSelectionRectangle.hpp b/src/slic3r/GUI/GLSelectionRectangle.hpp index d9869771ef..ae0b7a097e 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.hpp +++ b/src/slic3r/GUI/GLSelectionRectangle.hpp @@ -2,6 +2,9 @@ #define slic3r_GLSelectionRectangle_hpp_ #include "libslic3r/Point.hpp" +#if ENABLE_GLBEGIN_GLEND_REMOVAL +#include "GLModel.hpp" +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL namespace Slic3r { namespace GUI { @@ -30,7 +33,7 @@ public: // Disables the rectangle. void stop_dragging(); - void render(const GLCanvas3D& canvas) const; + void render(const GLCanvas3D& canvas); bool is_dragging() const { return m_state != Off; } EState get_state() const { return m_state; } @@ -43,9 +46,14 @@ public: float get_bottom() const { return std::min(m_start_corner(1), m_end_corner(1)); } private: - EState m_state = Off; - Vec2d m_start_corner; - Vec2d m_end_corner; + EState m_state{ Off }; + Vec2d m_start_corner{ Vec2d::Zero() }; + Vec2d m_end_corner{ Vec2d::Zero() }; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel m_rectangle; + Vec2d m_old_start_corner{ Vec2d::Zero() }; + Vec2d m_old_end_corner{ Vec2d::Zero() }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL }; diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 9c1e936525..32b3d59601 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -4,6 +4,7 @@ #include "3DScene.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/format.hpp" +#include "libslic3r/Color.hpp" #include #include @@ -206,154 +207,114 @@ void GLShaderProgram::stop_using() const glsafe(::glUseProgram(0)); } -bool GLShaderProgram::set_uniform(const char* name, int value) const +void GLShaderProgram::set_uniform(int id, int value) const { - int id = get_uniform_location(name); - if (id >= 0) { - glsafe(::glUniform1i(id, static_cast(value))); - return true; - } - return false; + if (id >= 0) + glsafe(::glUniform1i(id, value)); } -bool GLShaderProgram::set_uniform(const char* name, bool value) const +void GLShaderProgram::set_uniform(int id, bool value) const { - return set_uniform(name, value ? 1 : 0); + set_uniform(id, value ? 1 : 0); } -bool GLShaderProgram::set_uniform(const char* name, float value) const +void GLShaderProgram::set_uniform(int id, float value) const { - int id = get_uniform_location(name); - if (id >= 0) { - glsafe(::glUniform1f(id, static_cast(value))); - return true; - } - return false; + if (id >= 0) + glsafe(::glUniform1f(id, value)); } -bool GLShaderProgram::set_uniform(const char* name, double value) const +void GLShaderProgram::set_uniform(int id, double value) const { - return set_uniform(name, static_cast(value)); + set_uniform(id, static_cast(value)); } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform2iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform3iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform4iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform2fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform4fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const float* value, size_t size) const +void GLShaderProgram::set_uniform(int id, const float* value, size_t size) const { - if (size == 1) - return set_uniform(name, value[0]); - else if (size < 5) { - int id = get_uniform_location(name); - if (id >= 0) { - if (size == 2) - glsafe(::glUniform2fv(id, 1, static_cast(value))); - else if (size == 3) - glsafe(::glUniform3fv(id, 1, static_cast(value))); - else - glsafe(::glUniform4fv(id, 1, static_cast(value))); - - return true; - } - } - return false; -} - -bool GLShaderProgram::set_uniform(const char* name, const Transform3f& value) const -{ - int id = get_uniform_location(name); if (id >= 0) { + if (size == 1) + set_uniform(id, value[0]); + else if (size == 2) + glsafe(::glUniform2fv(id, 1, static_cast(value))); + else if (size == 3) + glsafe(::glUniform3fv(id, 1, static_cast(value))); + else if (size == 4) + glsafe(::glUniform4fv(id, 1, static_cast(value))); + } +} + +void GLShaderProgram::set_uniform(int id, const Transform3f& value) const +{ + if (id >= 0) glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.matrix().data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Transform3d& value) const +void GLShaderProgram::set_uniform(int id, const Transform3d& value) const { - return set_uniform(name, value.cast()); + set_uniform(id, value.cast()); } -bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const +void GLShaderProgram::set_uniform(int id, const Matrix3f& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const +void GLShaderProgram::set_uniform(int id, const Vec3f& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const +void GLShaderProgram::set_uniform(int id, const Vec3d& value) const { - return set_uniform(name, static_cast(value.cast())); + set_uniform(id, static_cast(value.cast())); +} + +void GLShaderProgram::set_uniform(int id, const ColorRGB& value) const +{ + set_uniform(id, value.data(), 3); +} + +void GLShaderProgram::set_uniform(int id, const ColorRGBA& value) const +{ + set_uniform(id, value.data(), 4); } int GLShaderProgram::get_attrib_location(const char* name) const diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index d7b92000df..06a5c00e5c 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -9,6 +9,9 @@ namespace Slic3r { +class ColorRGB; +class ColorRGBA; + class GLShaderProgram { public: @@ -44,22 +47,43 @@ public: void start_using() const; void stop_using() const; - bool set_uniform(const char* name, int value) const; - bool set_uniform(const char* name, bool value) const; - bool set_uniform(const char* name, float value) const; - bool set_uniform(const char* name, double value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const float* value, size_t size) const; - bool set_uniform(const char* name, const Transform3f& value) const; - bool set_uniform(const char* name, const Transform3d& value) const; - bool set_uniform(const char* name, const Matrix3f& value) const; - bool set_uniform(const char* name, const Vec3f& value) const; - bool set_uniform(const char* name, const Vec3d& value) const; + void set_uniform(const char* name, int value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, bool value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, float value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, double value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const float* value, size_t size) const { set_uniform(get_uniform_location(name), value, size); } + void set_uniform(const char* name, const Transform3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Transform3d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Matrix3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Vec3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Vec3d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const ColorRGB& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const ColorRGBA& value) const { set_uniform(get_uniform_location(name), value); } + + void set_uniform(int id, int value) const; + void set_uniform(int id, bool value) const; + void set_uniform(int id, float value) const; + void set_uniform(int id, double value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const float* value, size_t size) const; + void set_uniform(int id, const Transform3f& value) const; + void set_uniform(int id, const Transform3d& value) const; + void set_uniform(int id, const Matrix3f& value) const; + void set_uniform(int id, const Vec3f& value) const; + void set_uniform(int id, const Vec3d& value) const; + void set_uniform(int id, const ColorRGB& value) const; + void set_uniform(int id, const ColorRGBA& value) const; // returns -1 if not found int get_attrib_location(const char* name) const; diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 7fc03d4217..ae71e90afe 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -33,6 +33,14 @@ std::pair GLShadersManager::init() bool valid = true; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // basic shader, used to render all what was previously rendered using the immediate mode + valid &= append_shader("flat", { "flat.vs", "flat.fs" }); + // basic shader for textures, used to render textures + valid &= append_shader("flat_texture", { "flat_texture.vs", "flat_texture.fs" }); + // used to render 3D scene background + valid &= append_shader("background", { "background.vs", "background.fs" }); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); // used to render printbed diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index b97d8ae481..cbf760ff48 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -3,6 +3,10 @@ #include "3DScene.hpp" #include "OpenGLManager.hpp" +#if ENABLE_GLBEGIN_GLEND_REMOVAL +#include "GUI_App.hpp" +#include "GLModel.hpp" +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL #include @@ -120,11 +124,7 @@ void GLTexture::Compressor::compress() GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; GLTexture::GLTexture() - : m_id(0) - , m_width(0) - , m_height(0) - , m_source("") - , m_compressor(*this) + : m_compressor(*this) { } @@ -339,12 +339,39 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex(Vec2f(left, bottom), Vec2f(uvs.left_bottom.u, uvs.left_bottom.v)); + init_data.add_vertex(Vec2f(right, bottom), Vec2f(uvs.right_bottom.u, uvs.right_bottom.v)); + init_data.add_vertex(Vec2f(right, top), Vec2f(uvs.right_top.u, uvs.right_top.v)); + init_data.add_vertex(Vec2f(left, top), Vec2f(uvs.left_top.u, uvs.left_top.v)); + + // indices + init_data.add_ushort_triangle(0, 1, 2); + init_data.add_ushort_triangle(2, 3, 0); + + GLModel model; + model.init_from(std::move(init_data)); + + GLShaderProgram* shader = wxGetApp().get_shader("flat_texture"); + if (shader != nullptr) { + shader->start_using(); + model.render(); + shader->stop_using(); + } +#else ::glBegin(GL_QUADS); ::glTexCoord2f(uvs.left_bottom.u, uvs.left_bottom.v); ::glVertex2f(left, bottom); ::glTexCoord2f(uvs.right_bottom.u, uvs.right_bottom.v); ::glVertex2f(right, bottom); ::glTexCoord2f(uvs.right_top.u, uvs.right_top.v); ::glVertex2f(right, top); ::glTexCoord2f(uvs.left_top.u, uvs.left_top.v); ::glVertex2f(left, top); glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index c4063b93d4..b77edfeb0e 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -64,8 +64,8 @@ namespace GUI { struct UV { - float u; - float v; + float u{ 0.0f }; + float v{ 0.0f }; }; struct Quad_UVs @@ -79,9 +79,9 @@ namespace GUI { static Quad_UVs FullTextureUVs; protected: - unsigned int m_id; - int m_width; - int m_height; + unsigned int m_id{ 0 }; + int m_width{ 0 }; + int m_height{ 0 }; std::string m_source; Compressor m_compressor; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 68080cd6bd..a33742d149 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" @@ -482,6 +483,7 @@ struct FileWildcards { static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_STL */ { "STL files"sv, { ".stl"sv } }, /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, + /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, @@ -1598,8 +1600,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(); } @@ -1609,8 +1610,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(); } @@ -1814,6 +1814,7 @@ void GUI_App::update_ui_from_settings() m_force_colors_update = false; mainframe->force_color_changed(); mainframe->diff_dialog.force_color_changed(); + mainframe->preferences_dialog->force_color_changed(); mainframe->printhost_queue_dlg()->force_color_changed(); #ifdef _MSW_DARK_MODE update_scrolls(mainframe); @@ -2334,40 +2335,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuPreferences: { - bool app_layout_changed = false; - { - // the dialog needs to be destroyed before the call to recreate_GUI() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - PreferencesDialog dlg(mainframe); - dlg.ShowModal(); - app_layout_changed = dlg.settings_layout_changed(); - if (dlg.seq_top_layer_only_changed()) - this->plater_->refresh_print(); - - if (dlg.recreate_GUI()) { - recreate_GUI(_L("Restart application") + dots); - return; - } -#ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 - } - if (app_layout_changed) { - // hide full main_sizer for mainFrame - mainframe->GetSizer()->Show(false); - mainframe->update_layout(); - mainframe->select_tab(size_t(0)); - } + open_preferences(); break; } case ConfigMenuLanguage: @@ -2415,36 +2383,34 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } -void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option) +void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) { - bool app_layout_changed = false; - { - // the dialog needs to be destroyed before the call to recreate_GUI() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - PreferencesDialog dlg(mainframe, open_on_tab, highlight_option); - dlg.ShowModal(); - app_layout_changed = dlg.settings_layout_changed(); + mainframe->preferences_dialog->show(highlight_option, tab_name); + + if (mainframe->preferences_dialog->recreate_GUI()) + recreate_GUI(_L("Restart application") + dots); + #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) #else - if (dlg.seq_top_layer_only_changed()) + if (mainframe->preferences_dialog->seq_top_layer_only_changed()) #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - this->plater_->refresh_print(); + this->plater_->refresh_print(); + #ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); } - if (app_layout_changed) { + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); + } +#endif // _WIN32 + + if (mainframe->preferences_dialog->settings_layout_changed()) { // hide full main_sizer for mainFrame mainframe->GetSizer()->Show(false); mainframe->update_layout(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 0dcf655125..ac80fc8b2d 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -53,6 +53,7 @@ enum FileType { FT_STL, FT_OBJ, + FT_OBJECT, FT_AMF, FT_3MF, FT_GCODE, @@ -265,7 +266,7 @@ public: wxString current_language_code_safe() const; bool is_localized() const { return m_wxLocale->GetLocale() != "English"; } - void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string()); + void open_preferences(const std::string& highlight_option = std::string(), const std::string& tab_name = std::string()); virtual bool OnExceptionInMainLoop() override; // Calls wxLaunchDefaultBrowser if user confirms in dialog. diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index c3aaa2f4ef..7b3476d711 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -712,8 +712,8 @@ wxMenuItem* MenuFactory::append_menu_item_simplify(wxMenu* menu) void MenuFactory::append_menu_item_export_stl(wxMenu* menu) { - append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, "", - [](wxCommandEvent&) { plater()->export_stl(false, true); }, "", nullptr, + append_menu_item(menu, wxID_ANY, _L("Export as STL/OBJ") + dots, "", + [](wxCommandEvent&) { plater()->export_stl_obj(false, true); }, "", nullptr, []() { const Selection& selection = plater()->canvas3D()->get_selection(); return selection.is_single_full_instance() || selection.is_single_full_object() || selection.is_single_volume() || selection.is_single_modifier(); diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 92223a767d..000199f93a 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -9,6 +9,8 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/I18N.hpp" + // To show a message box if GUI initialization ends up with an exception thrown. #include diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a02abc8494..2b551eaca1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -239,6 +239,8 @@ ObjectList::ObjectList(wxWindow* parent) : ObjectList::~ObjectList() { + if (m_objects_model) + m_objects_model->DecRef(); } void ObjectList::set_min_height() @@ -1931,9 +1933,15 @@ void ObjectList::del_layers_from_object(const int obj_idx) bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { assert(idx >= 0); - if (obj_idx == 1000 || idx<0) +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (m_objects->empty() || int(m_objects->size()) <= obj_idx) + // Cannot delete a wipe tower + return false; +#else + if (obj_idx == 1000 || idx<0) // Cannot delete a wipe tower or volume with negative id return false; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL ModelObject* object = (*m_objects)[obj_idx]; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 51d69eaee5..b9b816b7be 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -191,7 +191,7 @@ private: public: ObjectList(wxWindow* parent); - ~ObjectList(); + ~ObjectList() override; void set_min_height(); void update_min_height(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 18e58efb88..4c1615dd46 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1103,9 +1103,14 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, { parent->set_focused_editor(nullptr); +#if ENABLE_OBJECT_MANIPULATOR_FOCUS + // if the widget loosing focus is not a manipulator field, call kill_focus + if (dynamic_cast(e.GetWindow()) == nullptr) +#else if (!m_enter_pressed) +#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS kill_focus(parent); - + e.Skip(); }, this->GetId()); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 156444a514..2cac5e3566 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -208,6 +208,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_layers_slider_sizer = create_layers_slider_sizer(); wxGetApp().UpdateDarkUI(m_bottom_toolbar_panel = new wxPanel(this)); +#if !ENABLE_PREVIEW_LAYOUT m_label_view_type = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("View")); #ifdef _WIN32 wxGetApp().UpdateDarkUI(m_choice_view_type = new BitmapComboBox(m_bottom_toolbar_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY)); @@ -221,6 +222,10 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_choice_view_type->Append(_L("Fan speed")); m_choice_view_type->Append(_L("Temperature")); m_choice_view_type->Append(_L("Volumetric flow rate")); +#if ENABLE_PREVIEW_LAYER_TIME + m_choice_view_type->Append(_L("Layer time (linear)")); + m_choice_view_type->Append(_L("Layer time (logarithmic)")); +#endif // ENABLE_PREVIEW_LAYER_TIME m_choice_view_type->Append(_L("Tool")); m_choice_view_type->Append(_L("Color Print")); m_choice_view_type->SetSelection(0); @@ -232,6 +237,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) #else long combo_style = wxCB_READONLY; #endif + m_combochecklist_features = new wxComboCtrl(); m_combochecklist_features->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, combo_style); std::string feature_items = GUI::into_u8( @@ -270,6 +276,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) get_option_type_string(OptionType::Legend) + "|1" ); Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); +#endif // !ENABLE_PREVIEW_LAYOUT m_left_sizer = new wxBoxSizer(wxVERTICAL); m_left_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); @@ -281,6 +288,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); +#if !ENABLE_PREVIEW_LAYOUT bottom_toolbar_sizer->AddSpacer(5); bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0); @@ -292,6 +300,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); bottom_toolbar_sizer->Hide(m_combochecklist_features); bottom_toolbar_sizer->AddSpacer(5); +#endif // !ENABLE_PREVIEW_LAYOUT bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0); m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer); @@ -353,7 +362,9 @@ void Preview::load_print(bool keep_z_range) else if (tech == ptSLA) load_print_as_sla(); +#if !ENABLE_PREVIEW_LAYOUT update_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT Layout(); } @@ -396,10 +407,12 @@ void Preview::refresh_print() void Preview::msw_rescale() { +#if !ENABLE_PREVIEW_LAYOUT #ifdef _WIN32 m_choice_view_type->Rescale(); m_choice_view_type->SetMinSize(m_choice_view_type->GetSize()); #endif +#endif // !ENABLE_PREVIEW_LAYOUT // rescale slider if (m_layers_slider != nullptr) m_layers_slider->msw_rescale(); if (m_moves_slider != nullptr) m_moves_slider->msw_rescale(); @@ -417,11 +430,13 @@ void Preview::sys_color_changed() wxWindowUpdateLocker noUpdates(this); wxGetApp().UpdateAllStaticTextDarkUI(m_bottom_toolbar_panel); +#if !ENABLE_PREVIEW_LAYOUT wxGetApp().UpdateDarkUI(m_choice_view_type); wxGetApp().UpdateDarkUI(m_combochecklist_features); wxGetApp().UpdateDarkUI(static_cast(m_combochecklist_features->GetPopupControl())); wxGetApp().UpdateDarkUI(m_combochecklist_options); wxGetApp().UpdateDarkUI(static_cast(m_combochecklist_options->GetPopupControl())); +#endif // !ENABLE_PREVIEW_LAYOUT #endif if (m_layers_slider != nullptr) @@ -445,19 +460,23 @@ void Preview::edit_layers_slider(wxKeyEvent& evt) void Preview::bind_event_handlers() { - this->Bind(wxEVT_SIZE, &Preview::on_size, this); + Bind(wxEVT_SIZE, &Preview::on_size, this); +#if !ENABLE_PREVIEW_LAYOUT m_choice_view_type->Bind(wxEVT_COMBOBOX, &Preview::on_choice_view_type, this); m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); m_combochecklist_options->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); +#endif // !ENABLE_PREVIEW_LAYOUT m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); } void Preview::unbind_event_handlers() { - this->Unbind(wxEVT_SIZE, &Preview::on_size, this); + Unbind(wxEVT_SIZE, &Preview::on_size, this); +#if !ENABLE_PREVIEW_LAYOUT m_choice_view_type->Unbind(wxEVT_COMBOBOX, &Preview::on_choice_view_type, this); m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); m_combochecklist_options->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); +#endif // !ENABLE_PREVIEW_LAYOUT m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); } @@ -478,6 +497,7 @@ void Preview::on_size(wxSizeEvent& evt) Refresh(); } +#if !ENABLE_PREVIEW_LAYOUT void Preview::on_choice_view_type(wxCommandEvent& evt) { int selection = m_choice_view_type->GetCurrentSelection(); @@ -544,6 +564,7 @@ void Preview::update_bottom_toolbar() } } } +#endif // !ENABLE_PREVIEW_LAYOUT wxBoxSizer* Preview::create_layers_slider_sizer() { @@ -953,10 +974,20 @@ void Preview::load_print_as_fff(bool keep_z_range) std::vector gcodes = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_canvas->get_custom_gcode_per_print_z(); +#if ENABLE_PREVIEW_LAYOUT + const GCodeViewer::EViewType choice = !gcodes.empty() ? + GCodeViewer::EViewType::ColorPrint : + (number_extruders > 1) ? GCodeViewer::EViewType::Tool : GCodeViewer::EViewType::FeatureType; + if (choice != gcode_view_type) { + m_canvas->set_gcode_view_preview_type(choice); + if (wxGetApp().is_gcode_viewer()) + m_keep_current_preview_type = true; + refresh_print(); + } +#else const wxString choice = !gcodes.empty() ? _L("Color Print") : (number_extruders > 1) ? _L("Tool") : _L("Feature type"); - int type = m_choice_view_type->FindString(choice); if (m_choice_view_type->GetSelection() != type) { if (0 <= type && type < static_cast(GCodeViewer::EViewType::Count)) { @@ -967,6 +998,7 @@ void Preview::load_print_as_fff(bool keep_z_range) refresh_print(); } } +#endif // ENABLE_PREVIEW_LAYOUT } if (zs.empty()) { @@ -1042,6 +1074,7 @@ void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event) m_canvas->render(); } +#if !ENABLE_PREVIEW_LAYOUT wxString Preview::get_option_type_string(OptionType type) const { switch (type) @@ -1061,6 +1094,7 @@ wxString Preview::get_option_type_string(OptionType type) const default: { return ""; } } } +#endif // !ENABLE_PREVIEW_LAYOUT } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 42246aa181..d9c32e8293 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -80,16 +80,18 @@ class Preview : public wxPanel wxBoxSizer* m_left_sizer { nullptr }; wxBoxSizer* m_layers_slider_sizer { nullptr }; wxPanel* m_bottom_toolbar_panel { nullptr }; +#if !ENABLE_PREVIEW_LAYOUT wxStaticText* m_label_view_type { nullptr }; #ifdef _WIN32 BitmapComboBox* m_choice_view_type { nullptr }; #else wxComboBox* m_choice_view_type { nullptr }; #endif - wxStaticText* m_label_show { nullptr }; + wxStaticText* m_label_show{ nullptr }; wxComboCtrl* m_combochecklist_features { nullptr }; size_t m_combochecklist_features_pos { 0 }; wxComboCtrl* m_combochecklist_options { nullptr }; +#endif // !ENABLE_PREVIEW_LAYOUT DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; @@ -126,7 +128,9 @@ public: CustomGCodes, Shells, ToolMarker, +#if !ENABLE_PREVIEW_LAYOUT Legend +#endif // !ENABLE_PREVIEW_LAYOUT }; Preview(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, @@ -154,12 +158,18 @@ public: bool is_loaded() const { return m_loaded; } +#if !ENABLE_PREVIEW_LAYOUT void update_bottom_toolbar(); +#endif // !ENABLE_PREVIEW_LAYOUT void update_moves_slider(); void enable_moves_slider(bool enable); void move_moves_slider(wxKeyEvent& evt); void hide_layers_slider(); +#if ENABLE_PREVIEW_LAYOUT + void set_keep_current_preview_type(bool value) { m_keep_current_preview_type = value; } +#endif // ENABLE_PREVIEW_LAYOUT + private: bool init(wxWindow* parent, Bed3D& bed, Model* model); @@ -167,9 +177,11 @@ private: void unbind_event_handlers(); void on_size(wxSizeEvent& evt); +#if !ENABLE_PREVIEW_LAYOUT void on_choice_view_type(wxCommandEvent& evt); void on_combochecklist_features(wxCommandEvent& evt); void on_combochecklist_options(wxCommandEvent& evt); +#endif // !ENABLE_PREVIEW_LAYOUT // Create/Update/Reset double slider on 3dPreview wxBoxSizer* create_layers_slider_sizer(); @@ -186,7 +198,9 @@ private: void on_layers_slider_scroll_changed(wxCommandEvent& event); void on_moves_slider_scroll_changed(wxCommandEvent& event); +#if !ENABLE_PREVIEW_LAYOUT wxString get_option_type_string(OptionType type) const; +#endif // !ENABLE_PREVIEW_LAYOUT }; } // namespace GUI diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index a5ba218b52..d7d3529fee 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -406,14 +406,6 @@ public: std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics); -inline int hex_digit_to_int(const char c) -{ - return - (c >= '0' && c <= '9') ? int(c - '0') : - (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : - (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; -} - class TaskTimer { std::chrono::milliseconds start_timer; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 393be1a4e8..e0e018a13c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -14,28 +14,9 @@ const float GLGizmoBase::Grabber::SizeFactor = 0.05f; const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; -GLGizmoBase::Grabber::Grabber() - : center(Vec3d::Zero()) - , angles(Vec3d::Zero()) - , dragging(false) - , enabled(true) +void GLGizmoBase::Grabber::render(bool hover, float size) { - color = { 1.0f, 1.0f, 1.0f, 1.0f }; -} - -void GLGizmoBase::Grabber::render(bool hover, float size) const -{ - std::array render_color; - if (hover) { - render_color[0] = (1.0f - color[0]); - render_color[1] = (1.0f - color[1]); - render_color[2] = (1.0f - color[2]); - render_color[3] = color[3]; - } - else - render_color = color; - - render(size, render_color, false); + render(size, hover ? complementary(color) : color, false); } float GLGizmoBase::Grabber::get_half_size(float size) const @@ -48,20 +29,27 @@ float GLGizmoBase::Grabber::get_dragging_half_size(float size) const return get_half_size(size) * DraggingScaleFactor; } -void GLGizmoBase::Grabber::render(float size, const std::array& render_color, bool picking) const +void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color, bool picking) { - if (! cube_initialized) { + if (!m_cube.is_initialized()) { // This cannot be done in constructor, OpenGL is not yet // initialized at that point (on Linux at least). - indexed_triangle_set mesh = its_make_cube(1., 1., 1.); - its_translate(mesh, Vec3f(-0.5, -0.5, -0.5)); - const_cast(cube).init_from(mesh, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); - const_cast(cube_initialized) = true; + indexed_triangle_set its = its_make_cube(1., 1., 1.); + its_translate(its, Vec3f(-0.5, -0.5, -0.5)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_cube.init_from(its); +#else + m_cube.init_from(its, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } - float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); + const float fullsize = 2.0f * (dragging ? get_dragging_half_size(size) : get_half_size(size)); - const_cast(&cube)->set_color(-1, render_color); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_cube.set_color(render_color); +#else + m_cube.set_color(-1, render_color); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glPushMatrix()); glsafe(::glTranslated(center.x(), center.y(), center.z())); @@ -69,11 +57,10 @@ void GLGizmoBase::Grabber::render(float size, const std::array& render glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); glsafe(::glScaled(fullsize, fullsize, fullsize)); - cube.render(); + m_cube.render(); glsafe(::glPopMatrix()); } - GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : m_parent(parent) , m_group_id(-1) @@ -90,25 +77,16 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u m_base_color = DEFAULT_BASE_COLOR; m_drag_color = DEFAULT_DRAG_COLOR; m_highlight_color = DEFAULT_HIGHLIGHT_COLOR; - m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24)); - m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.)); - m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.)); } void GLGizmoBase::set_hover_id(int id) { - if (m_grabbers.empty() || (id < (int)m_grabbers.size())) - { + if (m_grabbers.empty() || id < (int)m_grabbers.size()) { m_hover_id = id; on_set_hover_id(); } } -void GLGizmoBase::set_highlight_color(const std::array& color) -{ - m_highlight_color = color; -} - void GLGizmoBase::enable_grabber(unsigned int id) { if (id < m_grabbers.size()) @@ -162,22 +140,13 @@ bool GLGizmoBase::update_items_state() return res; }; -std::array GLGizmoBase::picking_color_component(unsigned int id) const +ColorRGBA GLGizmoBase::picking_color_component(unsigned int id) const { - static const float INV_255 = 1.0f / 255.0f; - id = BASE_ID - id; - if (m_group_id > -1) id -= m_group_id; - // color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass() - return std::array { - float((id >> 0) & 0xff) * INV_255, // red - float((id >> 8) & 0xff) * INV_255, // green - float((id >> 16) & 0xff) * INV_255, // blue - float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff))* INV_255 // checksum for validating against unwanted alpha blending and multi sampling - }; + return picking_decode(id); } void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const @@ -201,15 +170,23 @@ void GLGizmoBase::render_grabbers(float size) const void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const { - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + const float mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); - for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) { - if (m_grabbers[i].enabled) { - std::array color = picking_color_component(i); - m_grabbers[i].color = color; - m_grabbers[i].render_for_picking(mean_size); + for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) { + if (m_grabbers[i].enabled) { + m_grabbers[i].color = picking_color_component(i); + m_grabbers[i].render_for_picking(mean_size); + } } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + shader->stop_using(); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } std::string GLGizmoBase::format(float value, unsigned int decimals) const @@ -244,22 +221,5 @@ std::string GLGizmoBase::get_name(bool include_shortcut) const } - -// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components -// were not interpolated by alpha blending or multi sampling. -unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) -{ - // 8 bit hash for the color - unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; - // Increase enthropy by a bit reversal - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - // Flip every second bit to increase the enthropy even more. - b ^= 0x55; - return b; -} - - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 66ca4fca27..99050a2525 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -2,6 +2,7 @@ #define slic3r_GLGizmoBase_hpp_ #include "libslic3r/Point.hpp" +#include "libslic3r/Color.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GLModel.hpp" @@ -18,15 +19,11 @@ class ModelObject; namespace GUI { -static const std::array DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f }; -static const std::array DEFAULT_DRAG_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; -static const std::array DEFAULT_HIGHLIGHT_COLOR = { 1.0f, 0.38f, 0.0f, 1.0f }; -static const std::array, 3> AXES_COLOR = {{ - { 0.75f, 0.0f, 0.0f, 1.0f }, - { 0.0f, 0.75f, 0.0f, 1.0f }, - { 0.0f, 0.0f, 0.75f, 1.0f } - }}; -static const std::array CONSTRAINED_COLOR = { 0.5f, 0.5f, 0.5f, 1.0f }; +static const ColorRGBA DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f }; +static const ColorRGBA DEFAULT_DRAG_COLOR = ColorRGBA::WHITE(); +static const ColorRGBA DEFAULT_HIGHLIGHT_COLOR = ColorRGBA::ORANGE(); +static const std::array AXES_COLOR = {{ ColorRGBA::X(), ColorRGBA::Y(), ColorRGBA::Z() }}; +static const ColorRGBA CONSTRAINED_COLOR = ColorRGBA::GRAY(); class ImGuiWrapper; class GLCanvas3D; @@ -48,25 +45,24 @@ protected: static const float MinHalfSize; static const float DraggingScaleFactor; - Vec3d center; - Vec3d angles; - std::array color; - bool enabled; - bool dragging; + bool enabled{ true }; + bool dragging{ false }; + Vec3d center{ Vec3d::Zero() }; + Vec3d angles{ Vec3d::Zero() }; + ColorRGBA color{ ColorRGBA::WHITE() }; - Grabber(); + Grabber() = default; - void render(bool hover, float size) const; - void render_for_picking(float size) const { render(size, color, true); } + void render(bool hover, float size); + void render_for_picking(float size) { render(size, color, true); } float get_half_size(float size) const; float get_dragging_half_size(float size) const; private: - void render(float size, const std::array& render_color, bool picking) const; + void render(float size, const ColorRGBA& render_color, bool picking); - GLModel cube; - bool cube_initialized = false; + GLModel m_cube; }; public: @@ -97,17 +93,14 @@ protected: unsigned int m_sprite_id; int m_hover_id; bool m_dragging; - std::array m_base_color; - std::array m_drag_color; - std::array m_highlight_color; + ColorRGBA m_base_color; + ColorRGBA m_drag_color; + ColorRGBA m_highlight_color; mutable std::vector m_grabbers; ImGuiWrapper* m_imgui; bool m_first_input_window_render; mutable std::string m_tooltip; CommonGizmosDataPool* m_c; - GLModel m_cone; - GLModel m_cylinder; - GLModel m_sphere; public: GLGizmoBase(GLCanvas3D& parent, @@ -146,7 +139,7 @@ public: int get_hover_id() const { return m_hover_id; } void set_hover_id(int id); - void set_highlight_color(const std::array& color); + void set_highlight_color(const ColorRGBA& color) { m_highlight_color = color; } void enable_grabber(unsigned int id); void disable_grabber(unsigned int id); @@ -188,7 +181,8 @@ protected: // Returns the picking color for the given id, based on the BASE_ID constant // No check is made for clashing with other picking color (i.e. GLVolumes) - std::array picking_color_component(unsigned int id) const; + ColorRGBA picking_color_component(unsigned int id) const; + void render_grabbers(const BoundingBoxf3& box) const; void render_grabbers(float size) const; void render_grabbers_for_picking(const BoundingBoxf3& box) const; @@ -203,10 +197,6 @@ private: bool m_dirty; }; -// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components -// were not interpolated by alpha blending or multi sampling. -extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index bdc915e4a4..ab29e90262 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -23,7 +23,7 @@ namespace GUI { const double GLGizmoCut::Offset = 10.0; const double GLGizmoCut::Margin = 20.0; -const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 }; +static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -101,6 +101,38 @@ void GLGizmoCut::on_render() glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + const bool z_changed = std::abs(plane_center.z() - m_old_z) > EPSILON; + m_old_z = plane_center.z(); + + if (!m_plane.is_initialized() || z_changed) { + m_plane.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; + init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z())); + init_data.add_vertex(Vec3f(max_x, min_y, plane_center.z())); + init_data.add_vertex(Vec3f(max_x, max_y, plane_center.z())); + init_data.add_vertex(Vec3f(min_x, max_y, plane_center.z())); + + // indices + init_data.add_ushort_triangle(0, 1, 2); + init_data.add_ushort_triangle(2, 3, 0); + + m_plane.init_from(std::move(init_data)); + } + + m_plane.render(); +#else // Draw the cutting plane ::glBegin(GL_QUADS); ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); @@ -109,19 +141,45 @@ void GLGizmoCut::on_render() ::glVertex3f(max_x, max_y, plane_center.z()); ::glVertex3f(min_x, max_y, plane_center.z()); glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); - // TODO: draw cut part contour? + // Draw the grabber and the connecting line + m_grabbers[0].center = plane_center; + m_grabbers[0].center.z() = plane_center.z() + Offset; - // Draw the grabber and the connecting line - m_grabbers[0].center = plane_center; - m_grabbers[0].center.z() = plane_center.z() + Offset; + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (!m_grabber_connection.is_initialized() || z_changed) { + m_grabber_connection.reset(); - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.color = ColorRGBA::YELLOW(); + init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex((Vec3f)plane_center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); + + // indices + init_data.add_ushort_line(0, 1); + + m_grabber_connection.init_from(std::move(init_data)); + } + + m_grabber_connection.render(); + + shader->stop_using(); + } + + shader = wxGetApp().get_shader("gouraud_light"); +#else glsafe(::glColor3f(1.0, 1.0, 0.0)); ::glBegin(GL_LINES); ::glVertex3dv(plane_center.data()); @@ -129,22 +187,32 @@ void GLGizmoCut::on_render() glsafe(::glEnd()); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); - m_grabbers[0].color = GrabberColor; - m_grabbers[0].render(m_hover_id == 0, (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0)); + 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(); + shader->stop_using(); + } - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); - glsafe(::glLineWidth(2.0f)); - m_cut_contours.contours.render(); - glsafe(::glPopMatrix()); -} +#if ENABLE_GLBEGIN_GLEND_REMOVAL + shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + glsafe(::glPushMatrix()); + glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); + glsafe(::glLineWidth(2.0f)); + m_cut_contours.contours.render(); + glsafe(::glPopMatrix()); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + shader->stop_using(); + } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + } void GLGizmoCut::on_render_for_picking() { @@ -302,7 +370,11 @@ void GLGizmoCut::update_contours() const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); if (!polys.empty()) { m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_cut_contours.contours.set_color(ColorRGBA::WHITE()); +#else m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } } else if (box.center() != m_cut_contours.position) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 4113632c04..ccf8732cf9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -13,7 +13,6 @@ class GLGizmoCut : public GLGizmoBase { static const double Offset; static const double Margin; - static const std::array GrabberColor; double m_cut_z{ 0.0 }; double m_max_z{ 0.0 }; @@ -23,6 +22,11 @@ class GLGizmoCut : public GLGizmoBase bool m_keep_upper{ true }; bool m_keep_lower{ true }; bool m_rotate_lower{ false }; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel m_plane; + GLModel m_grabber_connection; + float m_old_z{ 0.0f }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL struct CutContours { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index d79d193d94..66b6dcf609 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -69,9 +69,7 @@ bool GLGizmoFdmSupports::on_init() return true; } - - -void GLGizmoFdmSupports::render_painter_gizmo() const +void GLGizmoFdmSupports::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 4929714a2b..df9cdce56f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -11,7 +11,7 @@ public: GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} - void render_painter_gizmo() const override; + void render_painter_gizmo() override; protected: void on_render_input_window(float x, float y, float bottom_limit) override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 4413a4e429..fd32c68fc1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -13,6 +13,8 @@ namespace Slic3r { namespace GUI { +static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; +static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -74,11 +76,7 @@ void GLGizmoFlatten::on_render() if (this->is_plane_update_necessary()) update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { - if (i == m_hover_id) - glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.75f)); - else - glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.5f)); - + glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data())); if (m_planes[i].vbo.has_VBOs()) m_planes[i].vbo.render(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 8070cf27b2..be52ebcb9a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -20,7 +20,6 @@ namespace GUI { GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { - m_vbo_cylinder.init_from(its_make_cylinder(1., 1.)); } @@ -63,6 +62,9 @@ void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&) void GLGizmoHollow::on_render() { + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0)); + const Selection& selection = m_parent.get_selection(); const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); @@ -99,12 +101,21 @@ void GLGizmoHollow::on_render_for_picking() render_points(selection, true); } -void GLGizmoHollow::render_points(const Selection& selection, bool picking) const +void GLGizmoHollow::render_points(const Selection& selection, bool picking) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); +#else GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); if (shader) shader->start_using(); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); @@ -114,7 +125,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); glsafe(::glMultMatrixd(instance_matrix.data())); - std::array render_color; + ColorRGBA render_color; const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; size_t cache_size = drain_holes.size(); @@ -126,33 +137,29 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons continue; // First decide about the color of the point. - if (picking) { - std::array color = picking_color_component(i); - render_color = color; - } + if (picking) + render_color = picking_color_component(i); else { - if (size_t(m_hover_id) == i) { - render_color = {0.f, 1.f, 1.f, 1.f}; - } + if (size_t(m_hover_id) == i) + render_color = ColorRGBA::CYAN(); else if (m_c->hollowed_mesh() && i < m_c->hollowed_mesh()->get_drainholes().size() && m_c->hollowed_mesh()->get_drainholes()[i].failed) { - render_color = {1.f, 0.f, 0.f, .5f}; - } - else { // neigher hover nor picking - - render_color[0] = point_selected ? 1.0f : 1.f; - render_color[1] = point_selected ? 0.3f : 1.f; - render_color[2] = point_selected ? 0.3f : 1.f; - render_color[3] = 0.5f; + render_color = { 1.0f, 0.0f, 0.0f, 0.5f }; } + else // neither hover nor picking + render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); } - const_cast(&m_vbo_cylinder)->set_color(-1, render_color); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_cylinder.set_color(render_color); +#else + const_cast(&m_cylinder)->set_color(-1, render_color); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); + glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); if (vol->is_left_handed()) @@ -160,13 +167,13 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons // Matrices set, we can render the point mark now. Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); Eigen::AngleAxisd aa(q); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); glsafe(::glPushMatrix()); glsafe(::glTranslated(0., 0., -drain_hole.height)); glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - m_vbo_cylinder.render(); + m_cylinder.render(); glsafe(::glPopMatrix()); if (vol->is_left_handed()) @@ -177,8 +184,6 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons glsafe(::glPopMatrix()); } - - bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const { if (m_c->object_clipper()->get_position() == 0.) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 2cf08de2a0..fa89e0febc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -42,13 +42,14 @@ private: void on_render() override; void on_render_for_picking() override; - void render_points(const Selection& selection, bool picking = false) const; + void render_points(const Selection& selection, bool picking = false); void hollow_mesh(bool postpone_error_messages = false); bool unsaved_changes() const; ObjectID m_old_mo_id = -1; - GLModel m_vbo_cylinder; + GLModel m_cylinder; + float m_new_hole_radius = 2.f; // Size of a new hole. float m_new_hole_height = 6.f; mutable std::vector m_selected; // which holes are currently selected diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 69d5ff9ed0..4c204d0d4c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -56,18 +56,12 @@ bool GLGizmoMmuSegmentation::on_is_activable() const return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1; } -static std::vector> get_extruders_colors() +static std::vector get_extruders_colors() { - unsigned char rgb_color[3] = {}; - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - std::vector> colors_out(colors.size()); - for (const std::string &color : colors) { - Slic3r::GUI::BitmapCache::parse_color(color, rgb_color); - size_t color_idx = &color - &colors.front(); - colors_out[color_idx] = {float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f}; - } - - return colors_out; + std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + std::vector ret; + decode_colors(colors, ret); + return ret; } static std::vector get_extruders_names() @@ -136,7 +130,7 @@ bool GLGizmoMmuSegmentation::on_init() return true; } -void GLGizmoMmuSegmentation::render_painter_gizmo() const +void GLGizmoMmuSegmentation::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); @@ -212,18 +206,14 @@ void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const } } -static void render_extruders_combo(const std::string &label, - const std::vector &extruders, - const std::vector> &extruders_colors, - size_t &selection_idx) +static void render_extruders_combo(const std::string& label, + const std::vector& extruders, + const std::vector& extruders_colors, + size_t& selection_idx) { assert(!extruders_colors.empty()); assert(extruders_colors.size() == extruders_colors.size()); - auto convert_to_imu32 = [](const std::array &color) -> ImU32 { - return IM_COL32(uint8_t(color[0] * 255.f), uint8_t(color[1] * 255.f), uint8_t(color[2] * 255.f), uint8_t(color[3] * 255.f)); - }; - size_t selection_out = selection_idx; // It is necessary to use BeginGroup(). Otherwise, when using SameLine() is called, then other items will be drawn inside the combobox. ImGui::BeginGroup(); @@ -239,7 +229,7 @@ static void render_extruders_combo(const std::string &labe ImGui::SameLine(); ImGuiStyle &style = ImGui::GetStyle(); float height = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), convert_to_imu32(extruders_colors[extruder_idx])); + ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), ImGuiWrapper::to_ImU32(extruders_colors[extruder_idx])); ImGui::GetWindowDrawList()->AddRect(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), IM_COL32_BLACK); ImGui::SetCursorScreenPos(ImVec2(start_position.x + height + height / 2 + style.FramePadding.x, start_position.y)); @@ -257,7 +247,7 @@ static void render_extruders_combo(const std::string &labe ImVec2 p = ImGui::GetCursorScreenPos(); float height = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), convert_to_imu32(extruders_colors[selection_idx])); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), ImGuiWrapper::to_ImU32(extruders_colors[selection_idx])); ImGui::GetWindowDrawList()->AddRect(p, ImVec2(p.x + height + height / 2, p.y + height), IM_COL32_BLACK); ImGui::SetCursorScreenPos(ImVec2(p.x + height + height / 2 + style.FramePadding.x, p.y)); @@ -339,10 +329,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott render_extruders_combo("##first_color_combo", m_original_extruders_names, m_original_extruders_colors, m_first_selected_extruder_idx); ImGui::SameLine(); - const std::array &select_first_color = m_modified_extruders_colors[m_first_selected_extruder_idx]; - ImVec4 first_color = ImVec4(select_first_color[0], select_first_color[1], select_first_color[2], select_first_color[3]); - if(ImGui::ColorEdit4("First color##color_picker", (float*)&first_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) - m_modified_extruders_colors[m_first_selected_extruder_idx] = {first_color.x, first_color.y, first_color.z, first_color.w}; + const ColorRGBA& select_first_color = m_modified_extruders_colors[m_first_selected_extruder_idx]; + ImVec4 first_color = ImGuiWrapper::to_ImVec4(select_first_color); + if (ImGui::ColorEdit4("First color##color_picker", (float*)&first_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) + m_modified_extruders_colors[m_first_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(first_color); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("second_color")); @@ -351,10 +341,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott render_extruders_combo("##second_color_combo", m_original_extruders_names, m_original_extruders_colors, m_second_selected_extruder_idx); ImGui::SameLine(); - const std::array &select_second_color = m_modified_extruders_colors[m_second_selected_extruder_idx]; - ImVec4 second_color = ImVec4(select_second_color[0], select_second_color[1], select_second_color[2], select_second_color[3]); - if(ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) - m_modified_extruders_colors[m_second_selected_extruder_idx] = {second_color.x, second_color.y, second_color.z, second_color.w}; + const ColorRGBA& select_second_color = m_modified_extruders_colors[m_second_selected_extruder_idx]; + ImVec4 second_color = ImGuiWrapper::to_ImVec4(select_second_color); + if (ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) + m_modified_extruders_colors[m_second_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(second_color); const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -565,16 +555,18 @@ PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const return PainterGizmoType::MMU_SEGMENTATION; } -std::array GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const +ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const { - const std::array &color = m_modified_extruders_colors[m_first_selected_extruder_idx]; - return {color[0], color[1], color[2], 0.25f}; + ColorRGBA color = m_modified_extruders_colors[m_first_selected_extruder_idx]; + color.a(0.25f); + return color; } -std::array GLGizmoMmuSegmentation::get_cursor_sphere_right_button_color() const +ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_right_button_color() const { - const std::array &color = m_modified_extruders_colors[m_second_selected_extruder_idx]; - return {color[0], color[1], color[2], 0.25f}; + ColorRGBA color = m_modified_extruders_colors[m_second_selected_extruder_idx]; + color.a(0.25f); + return color; } void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 577db8ed5a..c294fafcf2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -62,7 +62,7 @@ public: class TriangleSelectorMmGui : public TriangleSelectorGUI { public: // Plus 1 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the indices above colors.size() are allocated for seed fill. - explicit TriangleSelectorMmGui(const TriangleMesh &mesh, const std::vector> &colors, const std::array &default_volume_color) + TriangleSelectorMmGui(const TriangleMesh& mesh, const std::vector& colors, const ColorRGBA& default_volume_color) : TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(2 * (colors.size() + 1)) {} ~TriangleSelectorMmGui() override = default; @@ -73,8 +73,8 @@ public: private: void update_render_data(); - const std::vector> &m_colors; - const std::array m_default_volume_color; + const std::vector& m_colors; + const ColorRGBA m_default_volume_color; GLMmSegmentationGizmo3DScene m_gizmo_scene; }; @@ -85,7 +85,7 @@ public: : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} ~GLGizmoMmuSegmentation() override = default; - void render_painter_gizmo() const override; + void render_painter_gizmo() override; void set_painter_gizmo_data(const Selection& selection) override; @@ -100,8 +100,8 @@ public: const float get_cursor_radius_min() const override { return CursorRadiusMin; } protected: - std::array get_cursor_sphere_left_button_color() const override; - std::array get_cursor_sphere_right_button_color() const override; + ColorRGBA get_cursor_sphere_left_button_color() const override; + ColorRGBA get_cursor_sphere_right_button_color() const override; EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType(m_first_selected_extruder_idx + 1); } EnforcerBlockerType get_right_button_state_type() const override { return EnforcerBlockerType(m_second_selected_extruder_idx + 1); } @@ -121,8 +121,8 @@ protected: size_t m_first_selected_extruder_idx = 0; size_t m_second_selected_extruder_idx = 1; std::vector m_original_extruders_names; - std::vector> m_original_extruders_colors; - std::vector> m_modified_extruders_colors; + std::vector m_original_extruders_colors; + std::vector m_modified_extruders_colors; std::vector m_original_volumes_extruder_idxs; static const constexpr float CursorRadiusMin = 0.1f; // cannot be zero diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index e73a85647e..fbbcb32560 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -14,14 +14,7 @@ const double GLGizmoMove3D::Offset = 10.0; GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) - , m_displacement(Vec3d::Zero()) - , m_snap_step(1.0) - , m_starting_drag_position(Vec3d::Zero()) - , m_starting_box_center(Vec3d::Zero()) - , m_starting_box_bottom_center(Vec3d::Zero()) -{ - m_vbo_cone.init_from(its_make_cone(1., 1., 2*PI/36)); -} +{} std::string GLGizmoMove3D::get_tooltip() const { @@ -89,6 +82,9 @@ void GLGizmoMove3D::on_update(const UpdateData& data) void GLGizmoMove3D::on_render() { + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 18.0)); + const Selection& selection = m_parent.get_selection(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); @@ -111,17 +107,60 @@ void GLGizmoMove3D::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - if (m_hover_id == -1) { - // draw axes - for (unsigned int i = 0; i < 3; ++i) { - if (m_grabbers[i].enabled) { - glsafe(::glColor4fv(AXES_COLOR[i].data())); - ::glBegin(GL_LINES); - ::glVertex3dv(center.data()); - ::glVertex3dv(m_grabbers[i].center.data()); - glsafe(::glEnd()); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + auto render_grabber_connection = [this, ¢er](unsigned int id) { + if (m_grabbers[id].enabled) { + if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(center)) { + m_grabber_connections[id].old_center = center; + m_grabber_connections[id].model.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.color = AXES_COLOR[id]; + init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex((Vec3f)center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[id].center.cast()); + + // indices + init_data.add_ushort_line(0, 1); + + m_grabber_connections[id].model.init_from(std::move(init_data)); } + + m_grabber_connections[id].model.render(); } + }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + if (m_hover_id == -1) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + // draw axes + for (unsigned int i = 0; i < 3; ++i) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + render_grabber_connection(i); +#else + if (m_grabbers[i].enabled) { + glsafe(::glColor4fv(AXES_COLOR[i].data())); + ::glBegin(GL_LINES); + ::glVertex3dv(center.data()); + ::glVertex3dv(m_grabbers[i].center.data()); + glsafe(::glEnd()); + } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + } + +#if ENABLE_GLBEGIN_GLEND_REMOVAL + shader->stop_using(); + } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // draw grabbers render_grabbers(box); @@ -132,6 +171,16 @@ void GLGizmoMove3D::on_render() } else { // draw axis +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + render_grabber_connection(m_hover_id); + shader->stop_using(); + } + + shader = wxGetApp().get_shader("gouraud_light"); +#else glsafe(::glColor4fv(AXES_COLOR[m_hover_id].data())); ::glBegin(GL_LINES); ::glVertex3dv(center.data()); @@ -139,6 +188,7 @@ void GLGizmoMove3D::on_render() glsafe(::glEnd()); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); @@ -188,28 +238,30 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const return projection; } -void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const +void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) { - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); - double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size(mean_size) : (double)m_grabbers[axis].get_half_size(mean_size); - - std::array color = m_grabbers[axis].color; - if (!picking && m_hover_id != -1) { - color[0] = 1.0f - color[0]; - color[1] = 1.0f - color[1]; - color[2] = 1.0f - color[2]; - color[3] = color[3]; - } + 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)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); +#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader == nullptr) return; - const_cast(&m_vbo_cone)->set_color(-1, color); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_cone.set_color((!picking && m_hover_id != -1) ? complementary(m_grabbers[axis].color) : m_grabbers[axis].color); + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); +#else + m_cone.set_color(-1, (!picking && m_hover_id != -1) ? complementary(m_grabbers[axis].color) : m_grabbers[axis].color); if (!picking) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glPushMatrix()); glsafe(::glTranslated(m_grabbers[axis].center.x(), m_grabbers[axis].center.y(), m_grabbers[axis].center.z())); @@ -220,14 +272,14 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box glsafe(::glTranslated(0.0, 0.0, 2.0 * size)); glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size)); - m_vbo_cone.render(); + m_cone.render(); glsafe(::glPopMatrix()); +#if !ENABLE_GLBEGIN_GLEND_REMOVAL if (! picking) +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL shader->stop_using(); } - - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index baa2df7391..2a75df8669 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -11,15 +11,21 @@ class GLGizmoMove3D : public GLGizmoBase { static const double Offset; - Vec3d m_displacement; + Vec3d m_displacement{ Vec3d::Zero() }; + double m_snap_step{ 1.0 }; + Vec3d m_starting_drag_position{ Vec3d::Zero() }; + Vec3d m_starting_box_center{ Vec3d::Zero() }; + Vec3d m_starting_box_bottom_center{ Vec3d::Zero() }; - double m_snap_step; - - Vec3d m_starting_drag_position; - Vec3d m_starting_box_center; - Vec3d m_starting_box_bottom_center; - - GLModel m_vbo_cone; + GLModel m_cone; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + struct GrabberConnection + { + GLModel model; + Vec3d old_center{ Vec3d::Zero() }; + }; + std::array m_grabber_connections; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL public: GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -44,7 +50,7 @@ protected: private: double calc_projection(const UpdateData& data) const; - void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; + void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 5bcc888ed8..4c76767bd7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -18,13 +18,17 @@ namespace Slic3r::GUI { + std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { - // Make sphere and save it into a vertex buffer. - m_vbo_sphere.load_its_flat_shading(its_make_sphere(1., (2*M_PI)/24.)); - m_vbo_sphere.finalize_geometry(true); +} + +GLGizmoPainterBase::~GLGizmoPainterBase() +{ + if (s_sphere != nullptr && s_sphere->has_VBOs()) + s_sphere->release_geometry(); } void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) @@ -109,8 +113,7 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const } } - -void GLGizmoPainterBase::render_cursor() const +void GLGizmoPainterBase::render_cursor() { // First check that the mouse pointer is on an object. const ModelObject* mo = m_c->selection_info()->model_object(); @@ -137,26 +140,26 @@ void GLGizmoPainterBase::render_cursor() const } } - - -void GLGizmoPainterBase::render_cursor_circle() const +void GLGizmoPainterBase::render_cursor_circle() { const Camera &camera = wxGetApp().plater()->get_camera(); - auto zoom = (float) camera.get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + const float zoom = float(camera.get_zoom()); + const float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - Size cnv_size = m_parent.get_canvas_size(); - float cnv_half_width = 0.5f * (float) cnv_size.get_width(); - float cnv_half_height = 0.5f * (float) cnv_size.get_height(); - if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) + const Size cnv_size = m_parent.get_canvas_size(); + const float cnv_half_width = 0.5f * float(cnv_size.get_width()); + const float cnv_half_height = 0.5f * float(cnv_size.get_height()); + if (cnv_half_width == 0.0f || cnv_half_height == 0.0f) return; - Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1)); - Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1)); + const Vec2d mouse_pos(m_parent.get_local_mouse_position().x(), m_parent.get_local_mouse_position().y()); + Vec2d center(mouse_pos.x() - cnv_half_width, cnv_half_height - mouse_pos.y()); center = center * inv_zoom; glsafe(::glLineWidth(1.5f)); - static const std::array color = {0.f, 1.f, 0.3f}; +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + static const std::array color = { 0.f, 1.f, 0.3f }; glsafe(::glColor3fv(color.data())); +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); @@ -164,17 +167,49 @@ void GLGizmoPainterBase::render_cursor_circle() const // ensure that the circle is renderered inside the frustrum glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); // ensure that the overlay fits the frustrum near z plane - double gui_scale = camera.get_gui_scale(); + const double gui_scale = camera.get_gui_scale(); glsafe(::glScaled(gui_scale, gui_scale, 1.0)); glsafe(::glPushAttrib(GL_ENABLE_BIT)); glsafe(::glLineStipple(4, 0xAAAA)); glsafe(::glEnable(GL_LINE_STIPPLE)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - m_cursor_radius) > EPSILON) { + m_old_center = center; + m_old_cursor_radius = m_cursor_radius; + m_circle.reset(); + + GLModel::Geometry init_data; + static const unsigned int StepsCount = 32; + static const float StepSize = 2.0f * float(PI) / float(StepsCount); + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f }; + init_data.vertices.reserve(StepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(StepsCount * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + indices + for (unsigned short i = 0; i < StepsCount; ++i) { + const float angle = float(i * StepSize); + init_data.add_vertex(Vec3f(center.x() + ::cos(angle) * m_cursor_radius, center.y() + ::sin(angle) * m_cursor_radius, 0.0f)); + init_data.add_ushort_index(i); + } + + m_circle.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + m_circle.render(); + shader->stop_using(); + } +#else ::glBegin(GL_LINE_LOOP); for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glPopAttrib()); glsafe(::glPopMatrix()); @@ -184,6 +219,12 @@ void GLGizmoPainterBase::render_cursor_circle() const void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const { + if (s_sphere == nullptr) { + s_sphere = std::make_shared(); + s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0)); + s_sphere->finalize_geometry(true); + } + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); @@ -197,14 +238,15 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const if (is_left_handed) glFrontFace(GL_CW); - std::array render_color = {0.f, 0.f, 0.f, 0.25f}; + ColorRGBA render_color = { 0.0f, 0.0f, 0.0f, 0.25f }; if (m_button_down == Button::Left) render_color = this->get_cursor_sphere_left_button_color(); else if (m_button_down == Button::Right) render_color = this->get_cursor_sphere_right_button_color(); glsafe(::glColor4fv(render_color.data())); - m_vbo_sphere.render(); + assert(s_sphere != nullptr); + s_sphere->render(); if (is_left_handed) glFrontFace(GL_CCW); @@ -698,15 +740,15 @@ TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed}); } -std::array TriangleSelectorGUI::get_seed_fill_color(const std::array &base_color) +ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color) { - return {base_color[0] * 0.75f, base_color[1] * 0.75f, base_color[2] * 0.75f, 1.f}; + return saturate(base_color, 0.75f); } void TriangleSelectorGUI::render(ImGuiWrapper* imgui) { - static constexpr std::array enforcers_color{0.47f, 0.47f, 1.f, 1.f}; - static constexpr std::array blockers_color{1.f, 0.44f, 0.44f, 1.f}; + static const ColorRGBA enforcers_color = { 0.47f, 0.47f, 1.0f, 1.0f }; + static const ColorRGBA blockers_color = { 1.0f, 0.44f, 0.44f, 1.0f }; if (m_update_render_data) { update_render_data(); @@ -730,7 +772,7 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) for (auto &iva : m_iva_seed_fills) if (iva.has_VBOs()) { size_t color_idx = &iva - &m_iva_seed_fills.front(); - const std::array &color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : color_idx == 2 ? blockers_color : GLVolume::NEUTRAL_COLOR); shader->set_uniform("uniform_color", color); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 44476a5e3a..079f3f08ed 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -12,6 +12,7 @@ #include #include +#include namespace Slic3r::GUI { @@ -85,7 +86,7 @@ public: protected: bool m_update_render_data = false; - static std::array get_seed_fill_color(const std::array &base_color); + static ColorRGBA get_seed_fill_color(const ColorRGBA& base_color); private: void update_render_data(); @@ -112,7 +113,7 @@ private: void on_render_for_picking() override {} public: GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - ~GLGizmoPainterBase() override = default; + virtual ~GLGizmoPainterBase() override; virtual void set_painter_gizmo_data(const Selection& selection); virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); @@ -120,7 +121,7 @@ public: // from usual on_render method allows to render them before transparent // objects, so they can be seen inside them. The usual on_render is called // after all volumes (including transparent ones) are rendered. - virtual void render_painter_gizmo() const = 0; + virtual void render_painter_gizmo() = 0; virtual const float get_cursor_radius_min() const { return CursorRadiusMin; } virtual const float get_cursor_radius_max() const { return CursorRadiusMax; } @@ -128,14 +129,14 @@ public: protected: virtual void render_triangles(const Selection& selection) const; - void render_cursor() const; - void render_cursor_circle() const; + void render_cursor(); + void render_cursor_circle(); void render_cursor_sphere(const Transform3d& trafo) const; virtual void update_model_object() const = 0; virtual void update_from_model_object() = 0; - virtual std::array get_cursor_sphere_left_button_color() const { return {0.f, 0.f, 1.f, 0.25f}; } - virtual std::array get_cursor_sphere_right_button_color() const { return {1.f, 0.f, 0.f, 0.25f}; } + virtual ColorRGBA get_cursor_sphere_left_button_color() const { return { 0.0f, 0.0f, 1.0f, 0.25f }; } + virtual ColorRGBA get_cursor_sphere_right_button_color() const { return { 1.0f, 0.0f, 0.0f, 0.25f }; } virtual EnforcerBlockerType get_left_button_state_type() const { return EnforcerBlockerType::ENFORCER; } virtual EnforcerBlockerType get_right_button_state_type() const { return EnforcerBlockerType::BLOCKER; } @@ -170,6 +171,12 @@ protected: bool m_paint_on_overhangs_only = false; float m_highlight_by_angle_threshold_deg = 0.f; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel m_circle; + Vec2d m_old_center{ Vec2d::Zero() }; + float m_old_cursor_radius{ 0.0f }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + static constexpr float SmartFillAngleMin = 0.0f; static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleStep = 1.f; @@ -202,7 +209,7 @@ private: const Camera& camera, const std::vector& trafo_matrices) const; - GLIndexedVertexArray m_vbo_sphere; + static std::shared_ptr s_sphere; bool m_internal_stack_active = false; bool m_schedule_update = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 61fe6e7095..9bedc86cc8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -17,10 +17,9 @@ namespace GUI { const float GLGizmoRotate::Offset = 5.0f; -const unsigned int GLGizmoRotate::CircleResolution = 64; const unsigned int GLGizmoRotate::AngleResolution = 64; const unsigned int GLGizmoRotate::ScaleStepsCount = 72; -const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; +const float GLGizmoRotate::ScaleStepRad = 2.0f * float(PI) / GLGizmoRotate::ScaleStepsCount; const unsigned int GLGizmoRotate::ScaleLongEvery = 2; const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius const unsigned int GLGizmoRotate::SnapRegionsCount = 8; @@ -29,33 +28,11 @@ const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) : GLGizmoBase(parent, "", -1) , m_axis(axis) - , m_angle(0.0) - , m_center(0.0, 0.0, 0.0) - , m_radius(0.0f) - , m_snap_coarse_in_radius(0.0f) - , m_snap_coarse_out_radius(0.0f) - , m_snap_fine_in_radius(0.0f) - , m_snap_fine_out_radius(0.0f) -{ -} - -GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) - : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) - , m_axis(other.m_axis) - , m_angle(other.m_angle) - , m_center(other.m_center) - , m_radius(other.m_radius) - , m_snap_coarse_in_radius(other.m_snap_coarse_in_radius) - , m_snap_coarse_out_radius(other.m_snap_coarse_out_radius) - , m_snap_fine_in_radius(other.m_snap_fine_in_radius) - , m_snap_fine_out_radius(other.m_snap_fine_out_radius) -{ -} - +{} void GLGizmoRotate::set_angle(double angle) { - if (std::abs(angle - 2.0 * (double)PI) < EPSILON) + if (std::abs(angle - 2.0 * double(PI)) < EPSILON) angle = 0.0; m_angle = angle; @@ -70,7 +47,7 @@ std::string GLGizmoRotate::get_tooltip() const case Y: { axis = "Y"; break; } case Z: { axis = "Z"; break; } } - return (m_hover_id == 0 || m_grabbers[0].dragging) ? axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) : ""; + return (m_hover_id == 0 || m_grabbers.front().dragging) ? axis + ": " + format(float(Geometry::rad2deg(m_angle)), 4) : ""; } bool GLGizmoRotate::on_init() @@ -92,34 +69,31 @@ void GLGizmoRotate::on_start_dragging() void GLGizmoRotate::on_update(const UpdateData& data) { - Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection())); + const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection())); - Vec2d orig_dir = Vec2d::UnitX(); - Vec2d new_dir = mouse_pos.normalized(); + const Vec2d orig_dir = Vec2d::UnitX(); + const Vec2d new_dir = mouse_pos.normalized(); double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0)); if (cross2(orig_dir, new_dir) < 0.0) theta = 2.0 * (double)PI - theta; - double len = mouse_pos.norm(); + const double len = mouse_pos.norm(); // snap to coarse snap region - if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius)) - { - double step = 2.0 * (double)PI / (double)SnapRegionsCount; - theta = step * (double)std::round(theta / step); + if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { + const double step = 2.0 * double(PI) / double(SnapRegionsCount); + theta = step * std::round(theta / step); } - else - { + else { // snap to fine snap region (scale) - if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius)) - { - double step = 2.0 * (double)PI / (double)ScaleStepsCount; - theta = step * (double)std::round(theta / step); + if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { + const double step = 2.0 * double(PI) / double(ScaleStepsCount); + theta = step * std::round(theta / step); } } - if (theta == 2.0 * (double)PI) + if (theta == 2.0 * double(PI)) theta = 0.0; m_angle = theta; @@ -127,13 +101,16 @@ void GLGizmoRotate::on_update(const UpdateData& data) void GLGizmoRotate::on_render() { - if (!m_grabbers[0].enabled) + if (!m_grabbers.front().enabled) return; + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); - if (m_hover_id != 0 && !m_grabbers[0].dragging) { + if (m_hover_id != 0 && !m_grabbers.front().dragging) { m_center = box.center(); m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; @@ -142,12 +119,41 @@ void GLGizmoRotate::on_render() m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth); } + const double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); + m_grabbers.front().center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); + m_grabbers.front().angles.z() = m_angle; + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); transform_to_local(selection); glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + const float radius = Offset + m_parent.get_selection().get_bounding_box().radius(); + const bool radius_changed = std::abs(m_old_radius - radius) > EPSILON; + m_old_radius = radius; + + ColorRGBA color((m_hover_id != -1) ? m_drag_color : m_highlight_color); + render_circle(color, radius_changed); + if (m_hover_id != -1) { + const bool hover_radius_changed = std::abs(m_old_hover_radius - radius) > EPSILON; + m_old_hover_radius = radius; + + render_scale(color, hover_radius_changed); + render_snap_radii(color, hover_radius_changed); + render_reference_radius(color, hover_radius_changed); + render_angle_arc(m_highlight_color, hover_radius_changed); + } + + render_grabber_connection(color, radius_changed); + shader->stop_using(); + } +#else glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color.data() : m_highlight_color.data())); render_circle(); @@ -162,6 +168,7 @@ void GLGizmoRotate::on_render() if (m_hover_id != -1) render_angle(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL render_grabber(box); render_grabber_extension(box, false); @@ -216,11 +223,36 @@ void GLGizmoRotate3D::load_rotoptimize_state() } } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLGizmoRotate::render_circle(const ColorRGBA& color, bool radius_changed) +#else void GLGizmoRotate::render_circle() const +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (!m_circle.is_initialized() || radius_changed) { + m_circle.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + indices + for (unsigned short i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * ScaleStepRad); + init_data.add_vertex(Vec3f(::cos(angle) * m_radius, ::sin(angle) * m_radius, 0.0f)); + init_data.add_ushort_index(i); + } + + m_circle.init_from(std::move(init_data)); + } + + m_circle.set_color(color); + m_circle.render(); +#else ::glBegin(GL_LINE_LOOP); - for (unsigned int i = 0; i < ScaleStepsCount; ++i) - { + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { float angle = (float)i * ScaleStepRad; float x = ::cos(angle) * m_radius; float y = ::sin(angle) * m_radius; @@ -228,56 +260,151 @@ void GLGizmoRotate::render_circle() const ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); } glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLGizmoRotate::render_scale(const ColorRGBA& color, bool radius_changed) +#else void GLGizmoRotate::render_scale() const +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL { - float out_radius_long = m_snap_fine_out_radius; - float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); + const float out_radius_long = m_snap_fine_out_radius; + const float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (!m_scale.is_initialized() || radius_changed) { + m_scale.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(2 * ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + indices + for (unsigned short i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * ScaleStepRad); + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * m_radius; + const float in_y = sina * m_radius; + const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; + const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; + + init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); + init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); + init_data.add_ushort_index(i * 2); + init_data.add_ushort_index(i * 2 + 1); + } + + m_scale.init_from(std::move(init_data)); + } + + m_scale.set_color(color); + m_scale.render(); +#else ::glBegin(GL_LINES); - for (unsigned int i = 0; i < ScaleStepsCount; ++i) - { - float angle = (float)i * ScaleStepRad; - float cosa = ::cos(angle); - float sina = ::sin(angle); - float in_x = cosa * m_radius; - float in_y = sina * m_radius; - float in_z = 0.0f; - float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; - float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; - float out_z = 0.0f; + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = (float)i * ScaleStepRad; + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * m_radius; + const float in_y = sina * m_radius; + const float in_z = 0.0f; + const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; + const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; + const float out_z = 0.0f; ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); } glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLGizmoRotate::render_snap_radii(const ColorRGBA& color, bool radius_changed) +#else void GLGizmoRotate::render_snap_radii() const +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL { - float step = 2.0f * (float)PI / (float)SnapRegionsCount; + const float step = 2.0f * float(PI) / float(SnapRegionsCount); + const float in_radius = m_radius / 3.0f; + const float out_radius = 2.0f * in_radius; - float in_radius = m_radius / 3.0f; - float out_radius = 2.0f * in_radius; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (!m_snap_radii.is_initialized() || radius_changed) { + m_snap_radii.reset(); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(2 * ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + indices + for (unsigned short i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * step); + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * in_radius; + const float in_y = sina * in_radius; + const float out_x = cosa * out_radius; + const float out_y = sina * out_radius; + + init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); + init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); + init_data.add_ushort_index(i * 2); + init_data.add_ushort_index(i * 2 + 1); + } + + m_snap_radii.init_from(std::move(init_data)); + } + + m_snap_radii.set_color(color); + m_snap_radii.render(); +#else ::glBegin(GL_LINES); - for (unsigned int i = 0; i < SnapRegionsCount; ++i) - { - float angle = (float)i * step; - float cosa = ::cos(angle); - float sina = ::sin(angle); - float in_x = cosa * in_radius; - float in_y = sina * in_radius; - float in_z = 0.0f; - float out_x = cosa * out_radius; - float out_y = sina * out_radius; - float out_z = 0.0f; + for (unsigned int i = 0; i < SnapRegionsCount; ++i) { + const float angle = (float)i * step; + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * in_radius; + const float in_y = sina * in_radius; + const float in_z = 0.0f; + const float out_x = cosa * out_radius; + const float out_y = sina * out_radius; + const float out_z = 0.0f; ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); } glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLGizmoRotate::render_reference_radius(const ColorRGBA& color, bool radius_changed) +{ + if (!m_reference_radius.is_initialized() || radius_changed) { + m_reference_radius.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(m_radius * (1.0f + GrabberOffset), 0.0f, 0.0f)); + + // indices + init_data.add_ushort_line(0, 1); + + m_reference_radius.init_from(std::move(init_data)); + } + + m_reference_radius.set_color(color); + m_reference_radius.render(); +} +#else void GLGizmoRotate::render_reference_radius() const { ::glBegin(GL_LINES); @@ -285,15 +412,41 @@ void GLGizmoRotate::render_reference_radius() const ::glVertex3f((GLfloat)(m_radius * (1.0f + GrabberOffset)), 0.0f, 0.0f); glsafe(::glEnd()); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLGizmoRotate::render_angle_arc(const ColorRGBA& color, bool radius_changed) +#else void GLGizmoRotate::render_angle() const +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL { - float step_angle = (float)m_angle / AngleResolution; - float ex_radius = m_radius * (1.0f + GrabberOffset); + const float step_angle = float(m_angle) / float(AngleResolution); + const float ex_radius = m_radius * (1.0f + GrabberOffset); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + if (!m_angle_arc.is_initialized() || radius_changed) { + m_angle_arc.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve((1 + AngleResolution) * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve((1 + AngleResolution) * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + indices + for (unsigned short i = 0; i <= AngleResolution; ++i) { + const float angle = float(i) * step_angle; + init_data.add_vertex(Vec3f(::cos(angle) * ex_radius, ::sin(angle) * ex_radius, 0.0f)); + init_data.add_ushort_index(i); + } + + m_angle_arc.init_from(std::move(init_data)); + } + + m_angle_arc.set_color(color); + m_angle_arc.render(); +#else ::glBegin(GL_LINE_STRIP); - for (unsigned int i = 0; i <= AngleResolution; ++i) - { + for (unsigned int i = 0; i <= AngleResolution; ++i) { float angle = (float)i * step_angle; float x = ::cos(angle) * ex_radius; float y = ::sin(angle) * ex_radius; @@ -301,13 +454,42 @@ void GLGizmoRotate::render_angle() const ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); } glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } -void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLGizmoRotate::render_grabber_connection(const ColorRGBA& color, bool radius_changed) { - double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); + if (!m_grabber_connection.model.is_initialized() || radius_changed || !m_grabber_connection.old_center.isApprox(m_grabbers.front().center)) { + m_grabber_connection.model.reset(); + m_grabber_connection.old_center = m_grabbers.front().center; + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex((Vec3f)m_grabbers.front().center.cast()); + + // indices + init_data.add_ushort_line(0, 1); + + m_grabber_connection.model.init_from(std::move(init_data)); + } + + m_grabber_connection.model.set_color(color); + m_grabber_connection.model.render(); +} +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + +void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) +{ +#if !ENABLE_GLBEGIN_GLEND_REMOVAL + const double grabber_radius = double(m_radius) * (1.0 + double(GrabberOffset)); m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); - m_grabbers[0].angles(2) = m_angle; + m_grabbers[0].angles.z() = m_angle; glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color.data() : m_highlight_color.data())); @@ -315,35 +497,42 @@ void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const ::glVertex3f(0.0f, 0.0f, 0.0f); ::glVertex3dv(m_grabbers[0].center.data()); glsafe(::glEnd()); +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL - m_grabbers[0].color = m_highlight_color; + m_grabbers.front().color = m_highlight_color; render_grabbers(box); } -void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const +void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) { - 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); + const float mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); + const double size = m_dragging ? double(m_grabbers.front().get_dragging_half_size(mean_size)) : double(m_grabbers.front().get_half_size(mean_size)); - std::array color = m_grabbers[0].color; - if (!picking && m_hover_id != -1) { - color[0] = 1.0f - color[0]; - color[1] = 1.0f - color[1]; - color[2] = 1.0f - color[2]; - } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); + if (shader == nullptr) + return; + m_cone.set_color((!picking && m_hover_id != -1) ? complementary(m_grabbers.front().color) : m_grabbers.front().color); + + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); +#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; - const_cast(&m_cone)->set_color(-1, color); + m_cone.set_color(-1, (!picking && m_hover_id != -1) ? complementary(m_grabbers.front().color) : m_grabbers.front().color); if (!picking) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + const Vec3d& center = m_grabbers.front().center; glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z())); + glsafe(::glTranslated(center.x(), center.y(), center.z())); glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); glsafe(::glTranslated(0.0, 0.0, 2.0 * size)); @@ -351,7 +540,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick m_cone.render(); glsafe(::glPopMatrix()); glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z())); + glsafe(::glTranslated(center.x(), center.y(), center.z())); glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); glsafe(::glTranslated(0.0, 0.0, 2.0 * size)); @@ -359,16 +548,18 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick m_cone.render(); glsafe(::glPopMatrix()); +#if !ENABLE_GLBEGIN_GLEND_REMOVAL if (! picking) +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL shader->stop_using(); } void GLGizmoRotate::transform_to_local(const Selection& selection) const { - glsafe(::glTranslated(m_center(0), m_center(1), m_center(2))); + glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) { - Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } @@ -397,7 +588,7 @@ void GLGizmoRotate::transform_to_local(const Selection& selection) const Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const { - double half_pi = 0.5 * (double)PI; + double half_pi = 0.5 * double(PI); Transform3d m = Transform3d::Identity(); @@ -433,11 +624,8 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) + , m_gizmos({ GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Z) }) { - m_gizmos.emplace_back(parent, GLGizmoRotate::X); - m_gizmos.emplace_back(parent, GLGizmoRotate::Y); - m_gizmos.emplace_back(parent, GLGizmoRotate::Z); - for (unsigned int i = 0; i < 3; ++i) { m_gizmos[i].set_group_id(i); } @@ -473,13 +661,13 @@ bool GLGizmoRotate3D::on_is_activable() const void GLGizmoRotate3D::on_start_dragging() { - if ((0 <= m_hover_id) && (m_hover_id < 3)) + if (0 <= m_hover_id && m_hover_id < 3) m_gizmos[m_hover_id].start_dragging(); } void GLGizmoRotate3D::on_stop_dragging() { - if ((0 <= m_hover_id) && (m_hover_id < 3)) + if (0 <= m_hover_id && m_hover_id < 3) m_gizmos[m_hover_id].stop_dragging(); } @@ -554,11 +742,12 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y}; ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x); - if (wxGetApp().plater()->is_any_job_running()) + if (!wxGetApp().plater()->get_ui_job_worker().is_idle()) imgui->disabled_begin(true); if ( imgui->button(btn_txt) ) { - wxGetApp().plater()->optimize_rotation(); + replace_job(wxGetApp().plater()->get_ui_job_worker(), + std::make_unique()); } imgui->disabled_end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 3245c4dbe8..bb33e0f731 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -2,8 +2,6 @@ #define slic3r_GLGizmoRotate_hpp_ #include "GLGizmoBase.hpp" -#include "../Jobs/RotoptimizeJob.hpp" - namespace Slic3r { namespace GUI { @@ -11,7 +9,6 @@ namespace GUI { class GLGizmoRotate : public GLGizmoBase { static const float Offset; - static const unsigned int CircleResolution; static const unsigned int AngleResolution; static const unsigned int ScaleStepsCount; static const float ScaleStepRad; @@ -30,19 +27,33 @@ public: private: Axis m_axis; - double m_angle; + double m_angle{ 0.0 }; + Vec3d m_center{ Vec3d::Zero() }; + float m_radius{ 0.0f }; + float m_snap_coarse_in_radius{ 0.0f }; + float m_snap_coarse_out_radius{ 0.0f }; + float m_snap_fine_in_radius{ 0.0f }; + float m_snap_fine_out_radius{ 0.0f }; - mutable Vec3d m_center; - mutable float m_radius; - - mutable float m_snap_coarse_in_radius; - mutable float m_snap_coarse_out_radius; - mutable float m_snap_fine_in_radius; - mutable float m_snap_fine_out_radius; + GLModel m_cone; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel m_circle; + GLModel m_scale; + GLModel m_snap_radii; + GLModel m_reference_radius; + GLModel m_angle_arc; + struct GrabberConnection + { + GLModel model; + Vec3d old_center{ Vec3d::Zero() }; + }; + GrabberConnection m_grabber_connection; + float m_old_radius{ 0.0f }; + float m_old_hover_radius{ 0.0f }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL public: GLGizmoRotate(GLCanvas3D& parent, Axis axis); - GLGizmoRotate(const GLGizmoRotate& other); virtual ~GLGizmoRotate() = default; double get_angle() const { return m_angle; } @@ -59,13 +70,22 @@ protected: void on_render_for_picking() override; private: +#if ENABLE_GLBEGIN_GLEND_REMOVAL + void render_circle(const ColorRGBA& color, bool radius_changed); + void render_scale(const ColorRGBA& color, bool radius_changed); + void render_snap_radii(const ColorRGBA& color, bool radius_changed); + void render_reference_radius(const ColorRGBA& color, bool radius_changed); + void render_angle_arc(const ColorRGBA& color, bool radius_changed); + void render_grabber_connection(const ColorRGBA& color, bool radius_changed); +#else void render_circle() const; void render_scale() const; void render_snap_radii() const; void render_reference_radius() const; void render_angle() const; - void render_grabber(const BoundingBoxf3& box) const; - void render_grabber_extension(const BoundingBoxf3& box, bool picking) const; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + void render_grabber(const BoundingBoxf3& box); + void render_grabber_extension(const BoundingBoxf3& box, bool picking); void transform_to_local(const Selection& selection) const; // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate @@ -74,7 +94,7 @@ private: class GLGizmoRotate3D : public GLGizmoBase { - std::vector m_gizmos; + std::array m_gizmos; public: GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -82,8 +102,7 @@ public: Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } - std::string get_tooltip() const override - { + std::string get_tooltip() const override { std::string tooltip = m_gizmos[X].get_tooltip(); if (tooltip.empty()) tooltip = m_gizmos[Y].get_tooltip(); @@ -95,38 +114,32 @@ public: protected: bool on_init() override; std::string on_get_name() const override; - void on_set_state() override - { + void on_set_state() override { for (GLGizmoRotate& g : m_gizmos) g.set_state(m_state); } - void on_set_hover_id() override - { + void on_set_hover_id() override { for (int i = 0; i < 3; ++i) m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } - void on_enable_grabber(unsigned int id) override - { + void on_enable_grabber(unsigned int id) override { if (id < 3) m_gizmos[id].enable_grabber(0); } - void on_disable_grabber(unsigned int id) override - { + void on_disable_grabber(unsigned int id) override { if (id < 3) m_gizmos[id].disable_grabber(0); } bool on_is_activable() const override; void on_start_dragging() override; void on_stop_dragging() override; - void on_update(const UpdateData& data) override - { + void on_update(const UpdateData& data) override { for (GLGizmoRotate& g : m_gizmos) { g.update(data); } } void on_render() override; - void on_render_for_picking() override - { + void on_render_for_picking() override { for (GLGizmoRotate& g : m_gizmos) { g.render_for_picking(); } @@ -136,10 +149,11 @@ protected: private: - class RotoptimzeWindow { + class RotoptimzeWindow + { ImGuiWrapper *m_imgui = nullptr; - public: + public: struct State { float accuracy = 1.f; int method_id = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index b97507166b..7d0e3ec347 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -14,11 +14,17 @@ namespace GUI { const float GLGizmoScale3D::Offset = 5.0f; GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) - , m_scale(Vec3d::Ones()) - , m_offset(Vec3d::Zero()) - , m_snap_step(0.05) +: GLGizmoBase(parent, icon_filename, sprite_id) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_grabber_connections[0].grabber_indices = { 0, 1 }; + m_grabber_connections[1].grabber_indices = { 2, 3 }; + m_grabber_connections[2].grabber_indices = { 4, 5 }; + m_grabber_connections[3].grabber_indices = { 6, 7 }; + m_grabber_connections[4].grabber_indices = { 7, 8 }; + m_grabber_connections[5].grabber_indices = { 8, 9 }; + m_grabber_connections[6].grabber_indices = { 9, 6 }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } std::string GLGizmoScale3D::get_tooltip() const @@ -35,17 +41,17 @@ std::string GLGizmoScale3D::get_tooltip() const scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast(); if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) - return "X: " + format(scale(0), 4) + "%"; + return "X: " + format(scale.x(), 4) + "%"; else if (m_hover_id == 2 || m_hover_id == 3 || m_grabbers[2].dragging || m_grabbers[3].dragging) - return "Y: " + format(scale(1), 4) + "%"; + return "Y: " + format(scale.y(), 4) + "%"; else if (m_hover_id == 4 || m_hover_id == 5 || m_grabbers[4].dragging || m_grabbers[5].dragging) - return "Z: " + format(scale(2), 4) + "%"; + return "Z: " + format(scale.z(), 4) + "%"; else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) { - std::string tooltip = "X: " + format(scale(0), 4) + "%\n"; - tooltip += "Y: " + format(scale(1), 4) + "%\n"; - tooltip += "Z: " + format(scale(2), 4) + "%"; + std::string tooltip = "X: " + format(scale.x(), 4) + "%\n"; + tooltip += "Y: " + format(scale.y(), 4) + "%\n"; + tooltip += "Z: " + format(scale.z(), 4) + "%"; return tooltip; } else @@ -54,8 +60,7 @@ std::string GLGizmoScale3D::get_tooltip() const bool GLGizmoScale3D::on_init() { - for (int i = 0; i < 10; ++i) - { + for (int i = 0; i < 10; ++i) { m_grabbers.push_back(Grabber()); } @@ -87,29 +92,28 @@ bool GLGizmoScale3D::on_is_activable() const void GLGizmoScale3D::on_start_dragging() { - if (m_hover_id != -1) - { + if (m_hover_id != -1) { m_starting.drag_position = m_grabbers[m_hover_id].center; m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); const Vec3d& center = m_starting.box.center(); - m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2)); - m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min(0), center(1), center(2)); - m_starting.pivots[2] = m_transform * Vec3d(center(0), m_starting.box.max(1), center(2)); - m_starting.pivots[3] = m_transform * Vec3d(center(0), m_starting.box.min(1), center(2)); - m_starting.pivots[4] = m_transform * Vec3d(center(0), center(1), m_starting.box.max(2)); - m_starting.pivots[5] = m_transform * Vec3d(center(0), center(1), m_starting.box.min(2)); + m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); + m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); + m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); + m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); } } void GLGizmoScale3D::on_update(const UpdateData& data) { - if ((m_hover_id == 0) || (m_hover_id == 1)) + if (m_hover_id == 0 || m_hover_id == 1) do_scale_along_axis(X, data); - else if ((m_hover_id == 2) || (m_hover_id == 3)) + else if (m_hover_id == 2 || m_hover_id == 3) do_scale_along_axis(Y, data); - else if ((m_hover_id == 4) || (m_hover_id == 5)) + else if (m_hover_id == 4 || m_hover_id == 5) do_scale_along_axis(Z, data); else if (m_hover_id >= 6) do_scale_uniform(data); @@ -169,28 +173,28 @@ void GLGizmoScale3D::on_render() bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); // x axis - m_grabbers[0].center = m_transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x; - m_grabbers[0].color = (ctrl_down && (m_hover_id == 1)) ? CONSTRAINED_COLOR : AXES_COLOR[0]; - m_grabbers[1].center = m_transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x; - m_grabbers[1].color = (ctrl_down && (m_hover_id == 0)) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + m_grabbers[0].center = m_transform * Vec3d(m_box.min.x(), center.y(), center.z()) - offset_x; + m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + m_grabbers[1].center = m_transform * Vec3d(m_box.max.x(), center.y(), center.z()) + offset_x; + m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; // y axis - m_grabbers[2].center = m_transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y; - m_grabbers[2].color = (ctrl_down && (m_hover_id == 3)) ? CONSTRAINED_COLOR : AXES_COLOR[1]; - m_grabbers[3].center = m_transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y; - m_grabbers[3].color = (ctrl_down && (m_hover_id == 2)) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y(), center.z()) - offset_y; + m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y(), center.z()) + offset_y; + m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; // z axis - m_grabbers[4].center = m_transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z; - m_grabbers[4].color = (ctrl_down && (m_hover_id == 5)) ? CONSTRAINED_COLOR : AXES_COLOR[2]; - m_grabbers[5].center = m_transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z; - m_grabbers[5].color = (ctrl_down && (m_hover_id == 4)) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z()) - offset_z; + m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z()) + offset_z; + m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; // uniform - m_grabbers[6].center = m_transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y; - m_grabbers[7].center = m_transform * Vec3d(m_box.max(0), m_box.min(1), center(2)) + offset_x - offset_y; - m_grabbers[8].center = m_transform * Vec3d(m_box.max(0), m_box.max(1), center(2)) + offset_x + offset_y; - m_grabbers[9].center = m_transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y; + m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; + m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; + m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; + m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } @@ -204,9 +208,27 @@ void GLGizmoScale3D::on_render() const BoundingBoxf3& selection_box = selection.get_bounding_box(); - float grabber_mean_size = (float)((selection_box.size()(0) + selection_box.size()(1) + selection_box.size()(2)) / 3.0); + float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0); if (m_hover_id == -1) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // draw connections + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + if (m_grabbers[0].enabled && m_grabbers[1].enabled) + render_grabbers_connection(0, 1, m_grabbers[0].color); + if (m_grabbers[2].enabled && m_grabbers[3].enabled) + render_grabbers_connection(2, 3, m_grabbers[2].color); + if (m_grabbers[4].enabled && m_grabbers[5].enabled) + render_grabbers_connection(4, 5, m_grabbers[4].color); + render_grabbers_connection(6, 7, m_base_color); + render_grabbers_connection(7, 8, m_base_color); + render_grabbers_connection(8, 9, m_base_color); + render_grabbers_connection(9, 6, m_base_color); + shader->stop_using(); + } +#else // draw connections if (m_grabbers[0].enabled && m_grabbers[1].enabled) { glsafe(::glColor4fv(m_grabbers[0].color.data())); @@ -225,55 +247,111 @@ void GLGizmoScale3D::on_render() render_grabbers_connection(7, 8); render_grabbers_connection(8, 9); render_grabbers_connection(9, 6); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + // draw grabbers render_grabbers(grabber_mean_size); } else if (m_hover_id == 0 || m_hover_id == 1) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // draw connections + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + render_grabbers_connection(0, 1, m_grabbers[0].color); + shader->stop_using(); + } + + // draw grabbers + shader = wxGetApp().get_shader("gouraud_light"); +#else // draw connection glsafe(::glColor4fv(m_grabbers[0].color.data())); render_grabbers_connection(0, 1); + // draw grabbers GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); - // draw grabbers m_grabbers[0].render(true, grabber_mean_size); m_grabbers[1].render(true, grabber_mean_size); shader->stop_using(); } } else if (m_hover_id == 2 || m_hover_id == 3) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // draw connections + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + render_grabbers_connection(2, 3, m_grabbers[2].color); + shader->stop_using(); + } + + // draw grabbers + shader = wxGetApp().get_shader("gouraud_light"); +#else // draw connection glsafe(::glColor4fv(m_grabbers[2].color.data())); render_grabbers_connection(2, 3); + // draw grabbers GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); - // draw grabbers m_grabbers[2].render(true, grabber_mean_size); m_grabbers[3].render(true, grabber_mean_size); shader->stop_using(); } } else if (m_hover_id == 4 || m_hover_id == 5) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // draw connections + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + render_grabbers_connection(4, 5, m_grabbers[4].color); + shader->stop_using(); + } + + // draw grabbers + shader = wxGetApp().get_shader("gouraud_light"); +#else // draw connection glsafe(::glColor4fv(m_grabbers[4].color.data())); render_grabbers_connection(4, 5); + // draw grabbers GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); - // draw grabbers m_grabbers[4].render(true, grabber_mean_size); m_grabbers[5].render(true, grabber_mean_size); shader->stop_using(); } } else if (m_hover_id >= 6) { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // draw connections + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + render_grabbers_connection(6, 7, m_drag_color); + render_grabbers_connection(7, 8, m_drag_color); + render_grabbers_connection(8, 9, m_drag_color); + render_grabbers_connection(9, 6, m_drag_color); + shader->stop_using(); + } + + // draw grabbers + shader = wxGetApp().get_shader("gouraud_light"); +#else // draw connection glsafe(::glColor4fv(m_drag_color.data())); render_grabbers_connection(6, 7); @@ -281,11 +359,12 @@ void GLGizmoScale3D::on_render() render_grabbers_connection(8, 9); render_grabbers_connection(9, 6); + // draw grabbers GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); - // draw grabbers for (int i = 6; i < 10; ++i) { m_grabbers[i].render(true, grabber_mean_size); } @@ -300,26 +379,65 @@ void GLGizmoScale3D::on_render_for_picking() render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color) +{ + auto grabber_connection = [this](unsigned int id_1, unsigned int id_2) { + for (int i = 0; i < int(m_grabber_connections.size()); ++i) { + if (m_grabber_connections[i].grabber_indices.first == id_1 && m_grabber_connections[i].grabber_indices.second == id_2) + return i; + } + return -1; + }; + + const int id = grabber_connection(id_1, id_2); + if (id == -1) + return; + + if (!m_grabber_connections[id].model.is_initialized() || + !m_grabber_connections[id].old_v1.isApprox(m_grabbers[id_1].center) || + !m_grabber_connections[id].old_v2.isApprox(m_grabbers[id_2].center)) { + m_grabber_connections[id].old_v1 = m_grabbers[id_1].center; + m_grabber_connections[id].old_v2 = m_grabbers[id_2].center; + m_grabber_connections[id].model.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); + init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + + // vertices + init_data.add_vertex((Vec3f)m_grabbers[id_1].center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[id_2].center.cast()); + + // indices + init_data.add_ushort_line(0, 1); + + m_grabber_connections[id].model.init_from(std::move(init_data)); + } + + m_grabber_connections[id].model.set_color(color); + m_grabber_connections[id].model.render(); +} +#else void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const { unsigned int grabbers_count = (unsigned int)m_grabbers.size(); - if ((id_1 < grabbers_count) && (id_2 < grabbers_count)) - { + if (id_1 < grabbers_count && id_2 < grabbers_count) { ::glBegin(GL_LINES); ::glVertex3dv(m_grabbers[id_1].center.data()); ::glVertex3dv(m_grabbers[id_2].center.data()); glsafe(::glEnd()); } } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { double ratio = calc_ratio(data); - if (ratio > 0.0) - { + if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; - if (m_starting.ctrl_down) - { + if (m_starting.ctrl_down) { double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); if (m_hover_id == 2 * axis) local_offset *= -1.0; @@ -343,8 +461,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) { double ratio = calc_ratio(data); - if (ratio > 0.0) - { + if (ratio > 0.0) { m_scale = m_starting.scale * ratio; m_offset = Vec3d::Zero(); } @@ -354,12 +471,11 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; - Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); + Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); Vec3d starting_vec = m_starting.drag_position - pivot; double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) - { + if (len_starting_vec != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 35615df4a7..79388abba8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -28,11 +28,22 @@ class GLGizmoScale3D : public GLGizmoBase mutable Transform3d m_transform; // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) mutable Transform3d m_offsets_transform; - Vec3d m_scale; - Vec3d m_offset; - double m_snap_step; + Vec3d m_scale{ Vec3d::Ones() }; + Vec3d m_offset{ Vec3d::Zero() }; + double m_snap_step{ 0.05 }; StartingData m_starting; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + struct GrabberConnection + { + GLModel model; + std::pair grabber_indices; + Vec3d old_v1{ Vec3d::Zero() }; + Vec3d old_v2{ Vec3d::Zero() }; + }; + std::array m_grabber_connections; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + public: GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -56,7 +67,11 @@ protected: virtual void on_render_for_picking() override; private: +#if ENABLE_GLBEGIN_GLEND_REMOVAL + void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color); +#else void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL void do_scale_along_axis(Axis axis, const UpdateData& data); void do_scale_uniform(const UpdateData& data); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 00921aa9c8..5f6cd7a95f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -52,9 +52,7 @@ std::string GLGizmoSeam::on_get_name() const return _u8L("Seam painting"); } - - -void GLGizmoSeam::render_painter_gizmo() const +void GLGizmoSeam::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index 408c2ec4c1..533683237d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -11,7 +11,7 @@ public: GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} - void render_painter_gizmo() const override; + void render_painter_gizmo() override; protected: void on_render_input_window(float x, float y, float bottom_limit) override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 65d73e7c62..1b4c751d4e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -36,33 +36,72 @@ static void call_after_if_active(std::function fn, GUI_App* app = &wxGet }); } -static ModelVolume* get_model_volume(const Selection& selection, Model& model) +static std::set get_volume_ids(const Selection &selection) { - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - // only one selected volume - if (idxs.size() != 1) - return nullptr; - const GLVolume* selected_volume = selection.get_volume(*idxs.begin()); - if (selected_volume == nullptr) - return nullptr; + const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); + const ModelObjectPtrs &model_objects = selection.get_model()->objects; + std::set result; + for (auto volume_id : volume_ids) { + const GLVolume *selected_volume = selection.get_volume(volume_id); + assert(selected_volume != nullptr); - const GLVolume::CompositeID& cid = selected_volume->composite_id; - const ModelObjectPtrs& objs = model.objects; - if (cid.object_id < 0 || objs.size() <= static_cast(cid.object_id)) - return nullptr; - const ModelObject* obj = objs[cid.object_id]; - if (cid.volume_id < 0 || obj->volumes.size() <= static_cast(cid.volume_id)) - return nullptr; - return obj->volumes[cid.volume_id]; + const GLVolume::CompositeID &cid = selected_volume->composite_id; + + assert(cid.object_id >= 0); + assert(model_objects.size() > static_cast(cid.object_id)); + + const ModelObject *obj = model_objects[cid.object_id]; + const ModelVolume *volume = obj->volumes[cid.volume_id]; + const ObjectID & id = volume->id(); + + // prevent selection of volume without inidces + if (volume->mesh().its.indices.empty()) continue; + + assert(result.find(id) == result.end()); + result.insert(id); + } + return result; +} + +// return ModelVolume from selection by object id +static ModelVolume *get_volume(const ObjectID &id, const Selection &selection) { + const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); + const ModelObjectPtrs &model_objects = selection.get_model()->objects; + for (auto volume_id : volume_ids) { + const GLVolume *selected_volume = selection.get_volume(volume_id); + const GLVolume::CompositeID &cid = selected_volume->composite_id; + ModelObject *obj = model_objects[cid.object_id]; + ModelVolume *volume = obj->volumes[cid.volume_id]; + if (id == volume->id()) return volume; + } + return nullptr; +} + +static std::string create_volumes_name(const std::set& ids, const Selection &selection){ + assert(!ids.empty()); + std::string name; + bool is_first = true; + for (const ObjectID &id : ids) { + if (is_first) + is_first = false; + else + name += " + "; + + const ModelVolume *volume = get_volume(id, selection); + assert(volume != nullptr); + name += volume->name; + } + return name; } GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, const std::string &icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, -1) - , m_volume(nullptr) , m_show_wireframe(false) , m_move_to_center(false) + , m_original_triangle_count(0) + , m_triangle_count(0) // translation for GUI size , tr_mesh_name(_u8L("Mesh name")) , tr_triangles(_u8L("Triangles")) @@ -75,15 +114,11 @@ GLGizmoSimplify::~GLGizmoSimplify() stop_worker_thread_request(); if (m_worker.joinable()) m_worker.join(); - m_glmodel.reset(); } bool GLGizmoSimplify::on_esc_key_down() { - return false; - /*if (!m_is_worker_running) - return false; - stop_worker_thread_request(); - return true;*/ + //close(); + return stop_worker_thread_request(); } // while opening needs GLGizmoSimplify to set window position @@ -147,8 +182,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi { create_gui_cfg(); const Selection &selection = m_parent.get_selection(); - const ModelVolume *act_volume = get_model_volume(selection, wxGetApp().plater()->model()); - if (act_volume == nullptr) { + auto act_volume_ids = get_volume_ids(selection); + if (act_volume_ids.empty()) { stop_worker_thread_request(); close(); if (! m_parent.get_selection().is_single_volume()) { @@ -163,83 +198,93 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi bool is_cancelling = false; bool is_worker_running = false; bool is_result_ready = false; - int progress = 0; + int progress = 0; { std::lock_guard lk(m_state_mutex); is_cancelling = m_state.status == State::cancelling; is_worker_running = m_state.status == State::running; - is_result_ready = bool(m_state.result); + is_result_ready = !m_state.result.empty(); progress = m_state.progress; } // Whether to trigger calculation after rendering is done. bool start_process = false; - - // Check selection of new volume + // Check selection of new volume (or change) // Do not reselect object when processing - if (act_volume != m_volume) { - bool change_window_position = (m_volume == nullptr); + if (m_volume_ids != act_volume_ids) { + bool change_window_position = m_volume_ids.empty(); // select different model // close suggestion notification auto notification_manager = wxGetApp().plater()->get_notification_manager(); - notification_manager->remove_simplify_suggestion_with_id(act_volume->get_object()->id()); + for (const auto &id : act_volume_ids) + notification_manager->remove_simplify_suggestion_with_id(id); - m_volume = act_volume; + m_volume_ids = std::move(act_volume_ids); + init_model(); + + // triangle count is calculated in init model + m_original_triangle_count = m_triangle_count; + + // Default value of configuration m_configuration.decimate_ratio = 50.; // default value - m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size()); - init_model(m_volume->mesh().its); + m_configuration.fix_count_by_ratio(m_original_triangle_count); + m_configuration.use_count = false; + + // Create volumes name to describe what will be simplified + std::string name = create_volumes_name(m_volume_ids, selection); + if (name.length() > m_gui_cfg->max_char_in_name) + name = name.substr(0, m_gui_cfg->max_char_in_name - 3) + "..."; + m_volumes_name = name; // Start processing. If we switched from another object, process will // stop the background thread and it will restart itself later. start_process = true; // set window position - if (m_move_to_center && change_window_position) { - m_move_to_center = false; - auto parent_size = m_parent.get_canvas_size(); - ImVec2 pos(parent_size.get_width() / 2 - m_gui_cfg->window_offset_x, - parent_size.get_height() / 2 - m_gui_cfg->window_offset_y); - ImGui::SetNextWindowPos(pos, ImGuiCond_Always); - }else if (change_window_position) { - ImVec2 pos = ImGui::GetMousePos(); - pos.x -= m_gui_cfg->window_offset_x; - pos.y -= m_gui_cfg->window_offset_y; - // minimal top left value - ImVec2 tl(m_gui_cfg->window_padding, m_gui_cfg->window_padding + m_parent.get_main_toolbar_height()); - if (pos.x < tl.x) pos.x = tl.x; - if (pos.y < tl.y) pos.y = tl.y; - // maximal bottom right value - auto parent_size = m_parent.get_canvas_size(); - ImVec2 br( - parent_size.get_width() - (2 * m_gui_cfg->window_offset_x + m_gui_cfg->window_padding), - parent_size.get_height() - (2 * m_gui_cfg->window_offset_y + m_gui_cfg->window_padding)); - if (pos.x > br.x) pos.x = br.x; - if (pos.y > br.y) pos.y = br.y; + if (change_window_position) { + ImVec2 pos; + Size parent_size = m_parent.get_canvas_size(); + if (m_move_to_center) { + m_move_to_center = false; + pos = ImVec2(parent_size.get_width() / 2 - m_gui_cfg->window_offset_x, + parent_size.get_height() / 2 - m_gui_cfg->window_offset_y); + } else { + // keep window wisible on canvas and close to mouse click + pos = ImGui::GetMousePos(); + pos.x -= m_gui_cfg->window_offset_x; + pos.y -= m_gui_cfg->window_offset_y; + // minimal top left value + ImVec2 tl(m_gui_cfg->window_padding, + m_gui_cfg->window_padding + m_parent.get_main_toolbar_height()); + if (pos.x < tl.x) pos.x = tl.x; + if (pos.y < tl.y) pos.y = tl.y; + // maximal bottom right value + ImVec2 br(parent_size.get_width() - (2 * m_gui_cfg->window_offset_x + m_gui_cfg->window_padding), + parent_size.get_height() -(2 * m_gui_cfg->window_offset_y + m_gui_cfg->window_padding)); + if (pos.x > br.x) pos.x = br.x; + if (pos.y > br.y) pos.y = br.y; + } ImGui::SetNextWindowPos(pos, ImGuiCond_Always); } } + bool is_multipart = (m_volume_ids.size() > 1); int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; m_imgui->begin(on_get_name(), flag); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":"); ImGui::SameLine(m_gui_cfg->top_left_width); - std::string name = m_volume->name; - if (name.length() > m_gui_cfg->max_char_in_name) - name = name.substr(0, m_gui_cfg->max_char_in_name - 3) + "..."; - m_imgui->text(name); + m_imgui->text(m_volumes_name); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":"); ImGui::SameLine(m_gui_cfg->top_left_width); - size_t orig_triangle_count = m_volume->mesh().its.indices.size(); - m_imgui->text(std::to_string(orig_triangle_count)); - + m_imgui->text(std::to_string(m_original_triangle_count)); ImGui::Separator(); - if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) { + if(ImGui::RadioButton("##use_error", !m_configuration.use_count) && !is_multipart) { m_configuration.use_count = !m_configuration.use_count; start_process = true; } @@ -270,17 +315,21 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } m_imgui->disabled_end(); // !use_count - if (ImGui::RadioButton("##use_count", m_configuration.use_count)) { + if (ImGui::RadioButton("##use_count", m_configuration.use_count) && !is_multipart) { m_configuration.use_count = !m_configuration.use_count; start_process = true; - } + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_multipart) + ImGui::SetTooltip("%s", GUI::format(_L( + "Multipart object can be simplified only by %1%. " + "If you want specify %2% process it separately."), + tr_detail_level, tr_decimate_ratio).c_str()); ImGui::SameLine(); // show preview result triangle count (percent) if (!m_configuration.use_count) { m_configuration.wanted_count = static_cast(m_triangle_count); m_configuration.decimate_ratio = - (1.0f - (m_configuration.wanted_count / (float) orig_triangle_count)) * 100.f; + (1.0f - (m_configuration.wanted_count / (float) m_original_triangle_count)) * 100.f; } m_imgui->disabled_begin(!m_configuration.use_count); @@ -295,7 +344,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_configuration.decimate_ratio = 0.01f; if (m_configuration.decimate_ratio > 100.f) m_configuration.decimate_ratio = 100.f; - m_configuration.fix_count_by_ratio(orig_triangle_count); + m_configuration.fix_count_by_ratio(m_original_triangle_count); start_process = true; } @@ -342,11 +391,13 @@ void GLGizmoSimplify::close() { gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); } -void GLGizmoSimplify::stop_worker_thread_request() +bool GLGizmoSimplify::stop_worker_thread_request() { std::lock_guard lk(m_state_mutex); - if (m_state.status == State::running) - m_state.status = State::Status::cancelling; + if (m_state.status != State::running) return false; + + m_state.status = State::Status::cancelling; + return true; } @@ -366,9 +417,11 @@ void GLGizmoSimplify::worker_finished() m_worker.join(); if (GLGizmoBase::m_state == Off) return; - if (m_state.result) - init_model(*m_state.result); - if (m_state.config != m_configuration || m_state.mv != m_volume) { + const auto &result = m_state.result; + if (!result.empty()) + update_model(result); + + if (m_state.config != m_configuration || m_state.volume_ids != m_volume_ids) { // Settings were changed, restart the worker immediately. process(); } @@ -377,16 +430,16 @@ void GLGizmoSimplify::worker_finished() void GLGizmoSimplify::process() { - if (m_volume == nullptr || m_volume->mesh().its.indices.empty()) - return; + if (m_volume_ids.empty()) return; + // m_volume->mesh().its.indices.empty() bool configs_match = false; bool result_valid = false; bool is_worker_running = false; { std::lock_guard lk(m_state_mutex); - configs_match = (m_volume == m_state.mv && m_state.config == m_configuration); - result_valid = bool(m_state.result); + configs_match = (m_volume_ids == m_state.volume_ids && m_state.config == m_configuration); + result_valid = !m_state.result.empty(); is_worker_running = m_state.status == State::running; } @@ -411,15 +464,20 @@ void GLGizmoSimplify::process() // Copy configuration that will be used. m_state.config = m_configuration; - m_state.mv = m_volume; + m_state.volume_ids = m_volume_ids; m_state.status = State::running; - // Create a copy of current mesh to pass to the worker thread. + // Create a copy of current meshes to pass to the worker thread. // Using unique_ptr instead of pass-by-value to avoid an extra // copy (which would happen when passing to std::thread). - auto its = std::make_unique(m_volume->mesh().its); - - m_worker = std::thread([this](std::unique_ptr its) { + const Selection& selection = m_parent.get_selection(); + State::Data its; + for (const auto &id : m_volume_ids) { + const ModelVolume *volume = get_volume(id, selection); + its[id] = std::make_unique(volume->mesh().its); // copy + } + + m_worker = std::thread([this](State::Data its) { // Checks that the UI thread did not request cancellation, throws if so. std::function throw_on_cancel = [this]() { @@ -446,13 +504,16 @@ void GLGizmoSimplify::process() if (! m_state.config.use_count) max_error = m_state.config.max_error; m_state.progress = 0; - m_state.result.reset(); + m_state.result.clear(); m_state.status = State::Status::running; } // Start the actual calculation. try { - its_quadric_edge_collapse(*its, triangle_count, &max_error, throw_on_cancel, statusfn); + for (const auto& it : its) { + float me = max_error; + its_quadric_edge_collapse(*it.second, triangle_count, &me, throw_on_cancel, statusfn); + } } catch (SimplifyCanceledException &) { std::lock_guard lk(m_state_mutex); m_state.status = State::idle; @@ -471,25 +532,33 @@ void GLGizmoSimplify::process() } void GLGizmoSimplify::apply_simplify() { + // worker must be stopped + assert(m_state.status == State::Status::idle); + + // check that there is NO change of volume + assert(m_state.volume_ids == m_volume_ids); const Selection& selection = m_parent.get_selection(); - int object_idx = selection.get_object_idx(); - auto plater = wxGetApp().plater(); - plater->take_snapshot(GUI::format(_u8L("Simplify %1%"), m_volume->name)); - plater->clear_before_change_mesh(object_idx); + plater->take_snapshot(_u8L("Simplify ") + create_volumes_name(m_volume_ids, selection)); + plater->clear_before_change_mesh(selection.get_object_idx()); - ModelVolume* mv = get_model_volume(selection, wxGetApp().model()); - assert(mv == m_volume); + for (const auto &item: m_state.result) { + const ObjectID &id = item.first; + const indexed_triangle_set &its = *item.second; + ModelVolume *volume = get_volume(id, selection); + assert(volume != nullptr); + ModelObject *obj = volume->get_object(); - mv->set_mesh(std::move(*m_state.result)); - m_state.result.reset(); - mv->calculate_convex_hull(); - mv->set_new_unique_id(); - mv->get_object()->invalidate_bounding_box(); - mv->get_object()->ensure_on_bed(true); // allow negative z - - // fix hollowing, sla support points, modifiers, ... + volume->set_mesh(std::move(its)); + volume->calculate_convex_hull(); + volume->set_new_unique_id(); + obj->invalidate_bounding_box(); + obj->ensure_on_bed(true); // allow negative z + } + m_state.result.clear(); + // fix hollowing, sla support points, modifiers, ... + int object_idx = selection.get_object_idx(); plater->changed_mesh(object_idx); // Fix warning icon in object list wxGetApp().obj_list()->update_item_error_icon(object_idx, -1); @@ -508,8 +577,8 @@ void GLGizmoSimplify::on_set_state() m_parent.toggle_model_objects_visibility(true); stop_worker_thread_request(); - m_volume = nullptr; // invalidate selected model - m_glmodel.reset(); + m_volume_ids.clear(); // invalidate selected model + m_glmodels.clear(); // free gpu memory } else if (GLGizmoBase::m_state == GLGizmoBase::On) { // when open by hyperlink it needs to show up request_rerender(); @@ -550,73 +619,154 @@ void GLGizmoSimplify::set_center_position() { m_move_to_center = true; } - -void GLGizmoSimplify::init_model(const indexed_triangle_set& its) +void GLGizmoSimplify::init_model() { - if (its.indices.empty()) - return; + // volume ids must be set before init model + assert(!m_volume_ids.empty()); - m_glmodel.reset(); - m_glmodel.init_from(its); m_parent.toggle_model_objects_visibility(true); // selected volume may have changed - m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(), - m_c->selection_info()->get_active_instance(), m_volume); - - if (const Selection&sel = m_parent.get_selection(); sel.get_volume_idxs().size() == 1) - m_glmodel.set_color(-1, sel.get_volume(*sel.get_volume_idxs().begin())->color); - m_triangle_count = its.indices.size(); + const auto info = m_c->selection_info(); + + const Selection &selection = m_parent.get_selection(); + Model & model = *selection.get_model(); + const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); + const ModelObjectPtrs &model_objects = model.objects; + + m_glmodels.clear(); + //m_glmodels.reserve(volume_ids.size()); + m_triangle_count = 0; + for (const ObjectID& id: m_volume_ids) { + + const GLVolume *selected_volume; + const ModelVolume *volume = nullptr; + for (auto volume_id : volume_ids) { + selected_volume = selection.get_volume(volume_id); + const GLVolume::CompositeID &cid = selected_volume->composite_id; + ModelObject * obj = model_objects[cid.object_id]; + ModelVolume * act_volume = obj->volumes[cid.volume_id]; + if (id == act_volume->id()) { + volume = act_volume; + break; + } + } + assert(volume != nullptr); + +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // set actual triangle count + m_triangle_count += volume->mesh().its.indices.size(); +#else + const indexed_triangle_set &its = volume->mesh().its; + + // set actual triangle count + m_triangle_count += its.indices.size(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + assert(m_glmodels.find(id) == m_glmodels.end()); + GLModel &glmodel = m_glmodels[id]; // create new glmodel +#if ENABLE_GLBEGIN_GLEND_REMOVAL + glmodel.init_from(volume->mesh()); + glmodel.set_color(selected_volume->color); +#else + glmodel.init_from(its); + glmodel.set_color(-1,selected_volume->color); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + m_parent.toggle_model_objects_visibility(false, info->model_object(), + info->get_active_instance(), + volume); + } +} + +void GLGizmoSimplify::update_model(const State::Data &data) +{ + // check that model exist + if (m_glmodels.empty()) return; + + // check that result is for actual gl models + size_t model_count = m_glmodels.size(); + if (data.size() != model_count) return; + + m_triangle_count = 0; + for (const auto &item : data) { + const indexed_triangle_set &its = *item.second; + + auto it = m_glmodels.find(item.first); + assert(it != m_glmodels.end()); + + GLModel &glmodel = it->second; + auto color = glmodel.get_color(); + // when not reset it keeps old shape + glmodel.reset(); + glmodel.init_from(its); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + glmodel.set_color(color); +#else + glmodel.set_color(-1, color); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + m_triangle_count += its.indices.size(); + } } void GLGizmoSimplify::on_render() { - if (! m_glmodel.is_initialized()) - return; - - const auto& selection = m_parent.get_selection(); - const auto& volume_idxs = selection.get_volume_idxs(); - if (volume_idxs.empty() || volume_idxs.size() != 1) return; - const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin()); - + if (m_glmodels.empty()) return; + + const Selection & selection = m_parent.get_selection(); + // Check that the GLVolume still belongs to the ModelObject we work on. - if (m_volume != get_model_volume(selection, wxGetApp().model())) - return; + if (m_volume_ids != get_volume_ids(selection)) return; - const Transform3d trafo_matrix = selected_volume->world_matrix(); - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); + const ModelObjectPtrs &model_objects = selection.get_model()->objects; + const Selection::IndicesList &volume_idxs = selection.get_volume_idxs(); - auto *gouraud_shader = wxGetApp().get_shader("gouraud_light"); - glsafe(::glPushAttrib(GL_DEPTH_TEST)); - glsafe(::glEnable(GL_DEPTH_TEST)); - gouraud_shader->start_using(); - m_glmodel.render(); - gouraud_shader->stop_using(); + // no need to render nothing + if (volume_idxs.empty()) return; - if (m_show_wireframe) { - auto* contour_shader = wxGetApp().get_shader("mm_contour"); - contour_shader->start_using(); - glsafe(::glLineWidth(1.0f)); - glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); - //ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); }); - //glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); - //glsafe(::glPolygonOffset(5.0, 5.0)); - m_glmodel.render(); - glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); - contour_shader->stop_using(); + // Iteration over selection because of world transformation matrix of object + for (auto volume_id : volume_idxs) { + const GLVolume *selected_volume = selection.get_volume(volume_id); + const GLVolume::CompositeID &cid = selected_volume->composite_id; + + ModelObject *obj = model_objects[cid.object_id]; + ModelVolume *volume = obj->volumes[cid.volume_id]; + + auto it = m_glmodels.find(volume->id()); + assert(it != m_glmodels.end()); + + GLModel &glmodel = it->second; + + const Transform3d trafo_matrix = selected_volume->world_matrix(); + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); + + auto *gouraud_shader = wxGetApp().get_shader("gouraud_light"); + glsafe(::glPushAttrib(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_DEPTH_TEST)); + gouraud_shader->start_using(); + glmodel.render(); + gouraud_shader->stop_using(); + + if (m_show_wireframe) { + auto *contour_shader = wxGetApp().get_shader("mm_contour"); + contour_shader->start_using(); + glsafe(::glLineWidth(1.0f)); + glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); + glmodel.render(); + glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); + contour_shader->stop_using(); + } + glsafe(::glPopAttrib()); + glsafe(::glPopMatrix()); } - - glsafe(::glPopAttrib()); - glsafe(::glPopMatrix()); } - CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const { return CommonGizmosDataID( int(CommonGizmosDataID::SelectionInfo)); } - void GLGizmoSimplify::Configuration::fix_count_by_ratio(size_t triangle_count) { if (decimate_ratio <= 0.f) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 39093e14d6..52269ba86b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -10,8 +10,9 @@ #include namespace Slic3r { -class ModelVolume; +class ModelObject; class Model; +class ObjectID; namespace GUI { class NotificationManager; // for simplify suggestion @@ -46,12 +47,11 @@ private: void close(); void process(); - void stop_worker_thread_request(); + bool stop_worker_thread_request(); void worker_finished(); void create_gui_cfg(); void request_rerender(bool force = false); - void init_model(const indexed_triangle_set& its); void set_center_position(); @@ -76,10 +76,14 @@ private: bool m_move_to_center; // opening gizmo - const ModelVolume *m_volume; // keep pointer to actual working volume + std::set m_volume_ids; // keep pointers to actual working volumes + std::string m_volumes_name; + size_t m_original_triangle_count; bool m_show_wireframe; - GLModel m_glmodel; + std::map m_glmodels; + + size_t m_triangle_count; // triangle count of the model currently shown // Timestamp of the last rerender request. Only accessed from UI thread. @@ -88,6 +92,8 @@ private: // Following struct is accessed by both UI and worker thread. // Accesses protected by a mutex. struct State { + //using Data = std::vector >; + using Data = std::map >; enum Status { idle, running, @@ -97,10 +103,15 @@ private: Status status = idle; int progress = 0; // percent of done work Configuration config; // Configuration we started with. - const ModelVolume* mv = nullptr; - std::unique_ptr result; + const ModelObject* mo = nullptr; + + Data result; + std::set volume_ids; // is same as result keys - store separate for faster check }; + void init_model(); // initialize glModels from selection + void update_model(const State::Data &data); + std::thread m_worker; std::mutex m_state_mutex; // guards m_state State m_state; // accessed by both threads diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index ab35cec9be..3406c58a29 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -77,6 +77,13 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S void GLGizmoSlaSupports::on_render() { + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); + if (!m_sphere.is_initialized()) + m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0)); + ModelObject* mo = m_c->selection_info()->model_object(); const Selection& selection = m_parent.get_selection(); @@ -109,7 +116,7 @@ void GLGizmoSlaSupports::on_render_for_picking() render_points(selection, true); } -void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const +void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) { size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); @@ -137,7 +144,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) glsafe(::glTranslated(0.0, 0.0, z_shift)); glsafe(::glMultMatrixd(instance_matrix.data())); - std::array render_color; + ColorRGBA render_color; for (size_t i = 0; i < cache_size; ++i) { const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false; @@ -167,8 +174,13 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) } } - const_cast(&m_cone)->set_color(-1, render_color); - const_cast(&m_sphere)->set_color(-1, render_color); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_cone.set_color(render_color); + m_sphere.set_color(render_color); +#else + m_cone.set_color(-1, render_color); + m_sphere.set_color(-1, render_color); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader && !picking) shader->set_uniform("emission_factor", 0.5f); @@ -219,11 +231,12 @@ 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; - const_cast(&m_cylinder)->set_color(-1, render_color); + render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_cylinder.set_color(render_color); +#else + m_cylinder.set_color(-1, render_color); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader) shader->set_uniform("emission_factor", 0.5f); for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 35e6a73089..49df659f48 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -77,7 +77,7 @@ private: void on_render() override; void on_render_for_picking() override; - void render_points(const Selection& selection, bool picking = false) const; + void render_points(const Selection& selection, bool picking = false); bool unsaved_changes() const; bool m_lock_unique_islands = false; @@ -91,6 +91,10 @@ private: std::vector m_normal_cache; // to restore after discarding changes or undo/redo ObjectID m_old_mo_id; + GLModel m_cone; + GLModel m_cylinder; + GLModel m_sphere; + // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index d2a7e0d73e..cc53f267c0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -216,8 +216,8 @@ void InstancesHider::render_cut() const if (mv->is_model_part()) glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); else { - const std::array& c = color_from_model_volume(*mv); - glsafe(::glColor4f(c[0], c[1], c[2], c[3])); + const ColorRGBA color = color_from_model_volume(*mv); + glsafe(::glColor4fv(color.data())); } glsafe(::glPushAttrib(GL_DEPTH_TEST)); glsafe(::glDisable(GL_DEPTH_TEST)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 514db73c02..55cbb0c308 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -475,7 +475,7 @@ void GLGizmosManager::render_current_gizmo() const m_gizmos[m_current]->render(); } -void GLGizmosManager::render_painter_gizmo() const +void GLGizmosManager::render_painter_gizmo() { // This function shall only be called when current gizmo is // derived from GLGizmoPainterBase. @@ -954,28 +954,27 @@ void GLGizmosManager::update_after_undo_redo(const UndoRedo::Snapshot& snapshot) void GLGizmosManager::render_background(float left, float top, float right, float bottom, float border) const { - unsigned int tex_id = m_background_texture.texture.get_id(); - float tex_width = (float)m_background_texture.texture.get_width(); - float tex_height = (float)m_background_texture.texture.get_height(); - if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) - { - float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; - float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + const unsigned int tex_id = m_background_texture.texture.get_id(); + const float tex_width = float(m_background_texture.texture.get_width()); + const float tex_height = float(m_background_texture.texture.get_height()); + if (tex_id != 0 && tex_width > 0 && tex_height > 0) { + const float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; + const float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; - float internal_left = left + border; - float internal_right = right - border; - float internal_top = top - border; - float internal_bottom = bottom + border; + const float internal_left = left + border; + const float internal_right = right - border; + const float internal_top = top - border; + const float internal_bottom = bottom + border; // float left_uv = 0.0f; - float right_uv = 1.0f; - float top_uv = 1.0f; - float bottom_uv = 0.0f; + const float right_uv = 1.0f; + const float top_uv = 1.0f; + const float bottom_uv = 0.0f; - float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; - float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; - float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; - float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; + const float internal_left_uv = float(m_background_texture.metadata.left) * inv_tex_width; + const float internal_right_uv = 1.0f - float(m_background_texture.metadata.right) * inv_tex_width; + const float internal_top_uv = 1.0f - float(m_background_texture.metadata.top) * inv_tex_height; + const float internal_bottom_uv = float(m_background_texture.metadata.bottom) * inv_tex_height; // top-left corner GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); @@ -1007,8 +1006,7 @@ void GLGizmosManager::render_background(float left, float top, float right, floa } void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_type) const -{ - +{ std::vector selectable_idxs = get_selectable_idxs(); if (selectable_idxs.empty()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index ed02574dfa..8d268723bc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -25,16 +25,26 @@ class CommonGizmosDataPool; class Rect { - float m_left; - float m_top; - float m_right; - float m_bottom; + float m_left{ 0.0f }; + float m_top{ 0.0f }; + float m_right{ 0.0f }; + float m_bottom{ 0.0f }; public: - Rect() : m_left(0.0f) , m_top(0.0f) , m_right(0.0f) , m_bottom(0.0f) {} - + Rect() = default; Rect(float left, float top, float right, float bottom) : m_left(left) , m_top(top) , m_right(right) , m_bottom(bottom) {} +#if ENABLE_GLBEGIN_GLEND_REMOVAL + bool operator == (const Rect& other) const { + if (std::abs(m_left - other.m_left) > EPSILON) return false; + if (std::abs(m_top - other.m_top) > EPSILON) return false; + if (std::abs(m_right - other.m_right) > EPSILON) return false; + if (std::abs(m_bottom - other.m_bottom) > EPSILON) return false; + return true; + } + bool operator != (const Rect& other) const { return !operator==(other); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + float get_left() const { return m_left; } void set_left(float left) { m_left = left; } @@ -222,7 +232,7 @@ public: void render_current_gizmo() const; void render_current_gizmo_for_picking_pass() const; - void render_painter_gizmo() const; + void render_painter_gizmo(); void render_overlay() const; diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index 14ebbc6a05..93e0fb3259 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -421,9 +421,9 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) m_loaded_hints.emplace_back(hint_data); // open preferences } else if(dict["hypertext_type"] == "preferences") { - int page = static_cast(std::atoi(dict["hypertext_preferences_page"].c_str())); + std::string page = dict["hypertext_preferences_page"]; std::string item = dict["hypertext_preferences_item"]; - HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page, item]() { wxGetApp().open_preferences(page, item); } }; + HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page, item]() { wxGetApp().open_preferences(item, page); } }; m_loaded_hints.emplace_back(hint_data); } else if (dict["hypertext_type"] == "plater") { std::string item = dict["hypertext_plater_item"]; @@ -928,7 +928,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp } if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - wxGetApp().open_preferences(2, "show_hints"); + wxGetApp().open_preferences("show_hints", "GUI"); } ImGui::PopStyleColor(5); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index c06c6156eb..4616eee85d 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -25,6 +25,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "3DScene.hpp" #include "GUI.hpp" #include "I18N.hpp" @@ -52,7 +53,22 @@ static const std::map font_icons = { {ImGui::PreferencesButton , "notification_preferences" }, {ImGui::PreferencesHoverButton, "notification_preferences_hover"}, {ImGui::SliderFloatEditBtnIcon, "edit_button" }, + {ImGui::SliderFloatEditBtnPressedIcon, "edit_button_pressed" }, +#if ENABLE_LEGEND_TOOLBAR_ICONS + {ImGui::LegendTravel , "legend_travel" }, + {ImGui::LegendWipe , "legend_wipe" }, + {ImGui::LegendRetract , "legend_retract" }, + {ImGui::LegendDeretract , "legend_deretract" }, + {ImGui::LegendSeams , "legend_seams" }, + {ImGui::LegendToolChanges , "legend_toolchanges" }, + {ImGui::LegendColorChanges , "legend_colorchanges" }, + {ImGui::LegendPausePrints , "legend_pauseprints" }, + {ImGui::LegendCustomGCodes , "legend_customgcodes" }, + {ImGui::LegendShells , "legend_shells" }, + {ImGui::LegendToolMarker , "legend_toolmarker" }, +#endif // ENABLE_LEGEND_TOOLBAR_ICONS }; + static const std::map font_icons_large = { {ImGui::CloseNotifButton , "notification_close" }, {ImGui::CloseNotifHoverButton , "notification_close_hover" }, @@ -78,14 +94,14 @@ static const std::map font_icons_extra_large = { }; -const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.33f, 0.33f, 0.33f, 1.0f }; const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.133f, 0.133f, 0.133f, 0.8f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.67f, 0.36f, 0.19f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = to_ImVec4(ColorRGBA::ORANGE()); +const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.13f, 0.13f, 0.13f, 0.8f }; const ImVec4 ImGuiWrapper::COL_BUTTON_BACKGROUND = COL_ORANGE_DARK; const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = COL_ORANGE_LIGHT; -const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED; +const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = COL_BUTTON_HOVERED; ImGuiWrapper::ImGuiWrapper() { @@ -370,10 +386,41 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active) return ImGui::RadioButton(label_utf8.c_str(), active); } -bool ImGuiWrapper::image_button() +#if ENABLE_PREVIEW_LAYOUT +bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool active, + std::function draw_callback) { - return false; + ImGuiWindow& window = *ImGui::GetCurrentWindow(); + if (window.SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window.GetID(name.c_str()); + + const ImVec2 pos = window.DC.CursorPos; + const ImRect total_bb(pos, pos + ImVec2(size, size + style.FramePadding.y * 2.0f)); + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + ImGui::MarkItemEdited(id); + + if (hovered) + window.DrawList->AddRect({ pos.x - 1.0f, pos.y - 1.0f }, { pos.x + size + 1.0f, pos.y + size + 1.0f }, ImGui::GetColorU32(ImGuiCol_CheckMark)); + + if (active) + window.DrawList->AddRect(pos, { pos.x + size, pos.y + size }, ImGui::GetColorU32(ImGuiCol_CheckMark)); + + draw_callback(window, pos, size); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window.DC.LastItemStatusFlags); + return pressed; } +#endif // ENABLE_PREVIEW_LAYOUT bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { @@ -497,6 +544,9 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float if (pos != std::string::npos) str_label = str_label.substr(0, pos) + str_label.substr(pos + 2); + // the current slider edit state needs to be detected here before calling SliderFloat() + bool slider_editing = ImGui::GetCurrentWindow()->GetID(str_label.c_str()) == ImGui::GetActiveID(); + bool ret = ImGui::SliderFloat(str_label.c_str(), v, v_min, v_max, format, power); m_last_slider_status.hovered = ImGui::IsItemHovered(); @@ -514,15 +564,42 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float if (show_edit_btn) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); ImGui::SameLine(); - std::wstring btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(str_label); + +#if ENABLE_LEGEND_TOOLBAR_ICONS + ImGuiIO& io = ImGui::GetIO(); + assert(io.Fonts->TexWidth > 0 && io.Fonts->TexHeight > 0); + float inv_tex_w = 1.0f / float(io.Fonts->TexWidth); + float inv_tex_h = 1.0f / float(io.Fonts->TexHeight); + + const ImFontAtlasCustomRect* const rect = GetTextureCustomRect(slider_editing ? ImGui::SliderFloatEditBtnPressedIcon : ImGui::SliderFloatEditBtnIcon); + const ImVec2 size = { float(rect->Width), float(rect->Height) }; + const ImVec2 uv0 = ImVec2(float(rect->X) * inv_tex_w, float(rect->Y) * inv_tex_h); + const ImVec2 uv1 = ImVec2(float(rect->X + rect->Width) * inv_tex_w, float(rect->Y + rect->Height) * inv_tex_h); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f }); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.5f, 0.5f, 0.5f, 1.0f }); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.5f, 0.5f, 0.5f, 1.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.4f, 0.4f, 0.4f, 1.0f }); + +#if ENABLE_LEGEND_TOOLBAR_ICONS + const ImTextureID tex_id = io.Fonts->TexID; + if (image_button(tex_id, size, uv0, uv1, -1, ImVec4(0.0, 0.0, 0.0, 0.0), ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags_PressedOnClick)) { + if (!slider_editing) + ImGui::SetKeyboardFocusHere(-1); + else + ImGui::ClearActiveID(); + this->set_requires_extra_frame(); + } +#else + std::wstring btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(str_label); if (ImGui::Button(into_u8(btn_name).c_str())) { ImGui::SetKeyboardFocusHere(-1); this->set_requires_extra_frame(); } +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + ImGui::PopStyleColor(3); + if (ImGui::IsItemHovered()) this->tooltip(into_u8(_L("Edit")).c_str(), max_tooltip_width); @@ -556,17 +633,65 @@ bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, fl return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power, clamp, tooltip, show_edit_btn); } -bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection) +static bool image_button_ex(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); + ImGui::ItemSize(bb); + if (!ImGui::ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, ImGui::GetColorU32(bg_col)); + window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, ImGui::GetColorU32(tint_col)); + + return pressed; +} + +bool ImGuiWrapper::image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + ImGui::PushID((void*)(intptr_t)user_texture_id); + const ImGuiID id = window->GetID("#image"); + ImGui::PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding; + return image_button_ex(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col, flags); +} + +bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags) { // this is to force the label to the left of the widget: - text(label); - ImGui::SameLine(); +#if ENABLE_PREVIEW_LAYOUT + if (!label.empty()) { +#endif // ENABLE_PREVIEW_LAYOUT + text(label); + ImGui::SameLine(); +#if ENABLE_PREVIEW_LAYOUT + } +#endif // ENABLE_PREVIEW_LAYOUT int selection_out = selection; bool res = false; const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : ""; - if (ImGui::BeginCombo("", selection_str)) { + if (ImGui::BeginCombo("", selection_str, flags)) { for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { selection_out = i; @@ -1010,6 +1135,34 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } +#if ENABLE_LEGEND_TOOLBAR_ICONS +ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id) +{ + auto item = m_custom_glyph_rects_ids.find(tex_id); + return (item != m_custom_glyph_rects_ids.end()) ? ImGui::GetIO().Fonts->GetCustomRectByIndex(m_custom_glyph_rects_ids[tex_id]) : nullptr; +} +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + +ImU32 ImGuiWrapper::to_ImU32(const ColorRGBA& color) +{ + return ImGui::GetColorU32({ color.r(), color.g(), color.b(), color.a() }); +} + +ImVec4 ImGuiWrapper::to_ImVec4(const ColorRGBA& color) +{ + return { color.r(), color.g(), color.b(), color.a() }; +} + +ColorRGBA ImGuiWrapper::from_ImU32(const ImU32& color) +{ + return from_ImVec4(ImGui::ColorConvertU32ToFloat4(color)); +} + +ColorRGBA ImGuiWrapper::from_ImVec4(const ImVec4& color) +{ + return { color.x, color.y, color.z, color.w }; +} + #ifdef __APPLE__ static const ImWchar ranges_keyboard_shortcuts[] = { @@ -1098,12 +1251,27 @@ void ImGuiWrapper::init_font(bool compress) int rect_id = io.Fonts->CustomRects.Size; // id of the rectangle added next // add rectangles for the icons to the font atlas +#if ENABLE_LEGEND_TOOLBAR_ICONS + for (auto& icon : font_icons) { + m_custom_glyph_rects_ids[icon.first] = + io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); + } + for (auto& icon : font_icons_large) { + m_custom_glyph_rects_ids[icon.first] = + io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); + } + for (auto& icon : font_icons_extra_large) { + m_custom_glyph_rects_ids[icon.first] = + io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); +} +#else for (auto& icon : font_icons) io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); for (auto& icon : font_icons_large) io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); for (auto& icon : font_icons_extra_large) io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS // Build texture atlas unsigned char* pixels; diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index d75d6529ab..f461bc970a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -9,15 +9,21 @@ #include #include "libslic3r/Point.hpp" +#include "libslic3r/Color.hpp" -namespace Slic3r {namespace Search { +namespace Slic3r { +namespace Search { struct OptionViewParameters; -}} +} // namespace Search +} // namespace Slic3r class wxString; class wxMouseEvent; class wxKeyEvent; +#if ENABLE_PREVIEW_LAYOUT +struct IMGUI_API ImGuiWindow; +#endif // ENABLE_PREVIEW_LAYOUT namespace Slic3r { namespace GUI { @@ -34,6 +40,9 @@ class ImGuiWrapper bool m_disabled{ false }; bool m_new_frame_open{ false }; bool m_requires_extra_frame{ false }; +#if ENABLE_LEGEND_TOOLBAR_ICONS + std::map m_custom_glyph_rects_ids; +#endif // ENABLE_LEGEND_TOOLBAR_ICONS std::string m_clipboard_text; public: @@ -83,7 +92,9 @@ public: bool button(const wxString &label); bool button(const wxString& label, float width, float height); bool radio_button(const wxString &label, bool active); - bool image_button(); +#if ENABLE_PREVIEW_LAYOUT + bool draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback); +#endif // ENABLE_PREVIEW_LAYOUT bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); @@ -106,7 +117,10 @@ public: bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true); bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true); - bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected + bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0.0, 0.0), const ImVec2& uv1 = ImVec2(1.0, 1.0), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0.0, 0.0, 0.0, 0.0), const ImVec4& tint_col = ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags flags = 0); + + // Use selection = -1 to not mark any option as selected + bool combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags = 0); bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel); void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str, Search::OptionViewParameters& view_params, int& selected, bool& edited, int& mouse_wheel, bool is_localized); @@ -124,6 +138,15 @@ public: void set_requires_extra_frame() { m_requires_extra_frame = true; } void reset_requires_extra_frame() { m_requires_extra_frame = false; } + static ImU32 to_ImU32(const ColorRGBA& color); + static ImVec4 to_ImVec4(const ColorRGBA& color); + static ColorRGBA from_ImU32(const ImU32& color); + static ColorRGBA from_ImVec4(const ImVec4& color); + +#if ENABLE_LEGEND_TOOLBAR_ICONS + ImFontAtlasCustomRect* GetTextureCustomRect(const wchar_t& tex_id); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + static const ImVec4 COL_GREY_DARK; static const ImVec4 COL_GREY_LIGHT; static const ImVec4 COL_ORANGE_DARK; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 2771f9d271..3c7dad0a61 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -162,49 +162,43 @@ void ArrangeJob::prepare() wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); } -void ArrangeJob::on_exception(const std::exception_ptr &eptr) +void ArrangeJob::process(Ctl &ctl) { - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (libnest2d::GeometryException &) { - show_error(m_plater, _(L("Could not arrange model objects! " - "Some geometries may be invalid."))); - } catch (std::exception &) { - PlaterJob::on_exception(eptr); - } -} + static const auto arrangestr = _u8L("Arranging"); -void ArrangeJob::process() -{ - static const auto arrangestr = _(L("Arranging")); + ctl.update_status(0, arrangestr); + ctl.call_on_main_thread([this]{ prepare(); }).wait();; arrangement::ArrangeParams params = get_arrange_params(m_plater); - auto count = unsigned(m_selected.size() + m_unprintable.size()); + auto count = unsigned(m_selected.size() + m_unprintable.size()); Points bedpts = get_bed_shape(*m_plater->config()); - - params.stopcondition = [this]() { return was_canceled(); }; - - params.progressind = [this, count](unsigned st) { + + params.stopcondition = [&ctl]() { return ctl.was_canceled(); }; + + params.progressind = [this, count, &ctl](unsigned st) { st += m_unprintable.size(); - if (st > 0) update_status(int(count - st), arrangestr); + if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr); }; + ctl.update_status(0, arrangestr); + arrangement::arrange(m_selected, m_unselected, bedpts, params); - params.progressind = [this, count](unsigned st) { - if (st > 0) update_status(int(count - st), arrangestr); + params.progressind = [this, count, &ctl](unsigned st) { + if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr); }; arrangement::arrange(m_unprintable, {}, bedpts, params); // finalize just here. - update_status(int(count), - was_canceled() ? _(L("Arranging canceled.")) - : _(L("Arranging done."))); + ctl.update_status(int(count) * 100 / status_range(), ctl.was_canceled() ? + _u8L("Arranging canceled.") : + _u8L("Arranging done.")); } +ArrangeJob::ArrangeJob() : m_plater{wxGetApp().plater()} {} + static std::string concat_strings(const std::set &strings, const std::string &delim = "\n") { @@ -215,10 +209,21 @@ static std::string concat_strings(const std::set &strings, }); } -void ArrangeJob::finalize() { - // Ignore the arrange result if aborted. - if (was_canceled()) return; - +void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { + try { + if (eptr) + std::rethrow_exception(eptr); + } catch (libnest2d::GeometryException &) { + show_error(m_plater, _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); + eptr = nullptr; + } catch(...) { + eptr = std::current_exception(); + } + + if (canceled || eptr) + return; + // Unprintable items go to the last virtual bed int beds = 0; @@ -250,8 +255,6 @@ void ArrangeJob::finalize() { _L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"), concat_strings(names, "\n"))); } - - Job::finalize(); } std::optional diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index a5ecc0c834..106cc57ddf 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -1,7 +1,9 @@ #ifndef ARRANGEJOB_HPP #define ARRANGEJOB_HPP -#include "PlaterJob.hpp" +#include + +#include "Job.hpp" #include "libslic3r/Arrange.hpp" namespace Slic3r { @@ -10,13 +12,16 @@ class ModelInstance; namespace GUI { -class ArrangeJob : public PlaterJob +class Plater; + +class ArrangeJob : public Job { using ArrangePolygon = arrangement::ArrangePolygon; using ArrangePolygons = arrangement::ArrangePolygons; ArrangePolygons m_selected, m_unselected, m_unprintable; std::vector m_unarranged; + Plater *m_plater; // clear m_selected and m_unselected, reserve space for next usage void clear_input(); @@ -30,25 +35,20 @@ class ArrangeJob : public PlaterJob ArrangePolygon get_arrange_poly_(ModelInstance *mi); -protected: - - void prepare() override; - - void on_exception(const std::exception_ptr &) override; - - void process() override; - public: - ArrangeJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} - int status_range() const override + void prepare(); + + void process(Ctl &ctl) override; + + ArrangeJob(); + + int status_range() const { return int(m_selected.size() + m_unprintable.size()); } - void finalize() override; + void finalize(bool canceled, std::exception_ptr &e) override; }; std::optional get_wipe_tower_arrangepoly(const Plater &); diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp new file mode 100644 index 0000000000..a4f22be559 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp @@ -0,0 +1,181 @@ +#include + +#include "BoostThreadWorker.hpp" + +namespace Slic3r { namespace GUI { + +void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner) +{ + switch(MsgType(get_type())) { + case Empty: break; + case Status: { + auto info = boost::get(m_data); + if (runner.get_pri()) { + runner.get_pri()->set_progress(info.status); + runner.get_pri()->set_status_text(info.msg.c_str()); + } + break; + } + case Finalize: { + auto& entry = boost::get(m_data); + entry.job->finalize(entry.canceled, entry.eptr); + + // Unhandled exceptions are rethrown without mercy. + if (entry.eptr) + std::rethrow_exception(entry.eptr); + + break; + } + case MainThreadCall: { + auto &calldata = boost::get(m_data); + calldata.fn(); + calldata.promise.set_value(); + + break; + } + } +} + +void BoostThreadWorker::run() +{ + bool stop = false; + while (!stop) { + m_input_queue + .consume_one(BlockingWait{0, &m_running}, [this, &stop](JobEntry &e) { + if (!e.job) + stop = true; + else { + m_canceled.store(false); + + try { + e.job->process(*this); + } catch (...) { + e.eptr = std::current_exception(); + } + + e.canceled = m_canceled.load(); + m_output_queue.push(std::move(e)); // finalization message + } + m_running.store(false); + }); + }; +} + +void BoostThreadWorker::update_status(int st, const std::string &msg) +{ + m_output_queue.push(st, msg); +} + +std::future BoostThreadWorker::call_on_main_thread(std::function fn) +{ + MainThreadCallData cbdata{std::move(fn), {}}; + std::future future = cbdata.promise.get_future(); + + m_output_queue.push(std::move(cbdata)); + + return future; +} + +BoostThreadWorker::BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes &attribs, + const char * name) + : m_progress(std::move(pri)), m_name{name} +{ + if (m_progress) + m_progress->set_cancel_callback([this](){ cancel(); }); + + m_thread = create_thread(attribs, [this] { this->run(); }); + + std::string nm{name}; + if (!nm.empty()) set_thread_name(m_thread, name); +} + +constexpr int ABORT_WAIT_MAX_MS = 10000; + +BoostThreadWorker::~BoostThreadWorker() +{ + bool joined = false; + try { + cancel_all(); + wait_for_idle(ABORT_WAIT_MAX_MS); + m_input_queue.push(JobEntry{nullptr}); + joined = join(ABORT_WAIT_MAX_MS); + } catch(...) {} + + if (!joined) + BOOST_LOG_TRIVIAL(error) + << "Could not join worker thread '" << m_name << "'"; +} + +bool BoostThreadWorker::join(int timeout_ms) +{ + if (!m_thread.joinable()) + return true; + + if (timeout_ms <= 0) { + m_thread.join(); + } + else if (m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) { + return true; + } + else + return false; + + return true; +} + +void BoostThreadWorker::process_events() +{ + while (m_output_queue.consume_one([this](WorkerMessage &msg) { + msg.deliver(*this); + })); +} + +bool BoostThreadWorker::wait_for_current_job(unsigned timeout_ms) +{ + bool ret = true; + + if (!is_idle()) { + bool was_finish = false; + bool timeout_reached = false; + while (!timeout_reached && !was_finish) { + timeout_reached = + !m_output_queue.consume_one(BlockingWait{timeout_ms}, + [this, &was_finish]( + WorkerMessage &msg) { + msg.deliver(*this); + if (msg.get_type() == + WorkerMessage::Finalize) + was_finish = true; + }); + } + + ret = !timeout_reached; + } + + return ret; +} + +bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms) +{ + bool timeout_reached = false; + while (!timeout_reached && !is_idle()) { + timeout_reached = !m_output_queue + .consume_one(BlockingWait{timeout_ms}, + [this](WorkerMessage &msg) { + msg.deliver(*this); + }); + } + + return !timeout_reached; +} + +bool BoostThreadWorker::push(std::unique_ptr job) +{ + if (job) + m_input_queue.push(JobEntry{std::move(job)}); + + return bool{job}; +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp new file mode 100644 index 0000000000..2fe39c0a7b --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp @@ -0,0 +1,140 @@ +#ifndef BOOSTTHREADWORKER_HPP +#define BOOSTTHREADWORKER_HPP + +#include + +#include "Worker.hpp" + +#include +#include + +#include "ThreadSafeQueue.hpp" + +namespace Slic3r { namespace GUI { + +// An implementation of the Worker interface which uses the boost::thread +// API and two thread safe message queues to communicate with the main thread +// back and forth. The queue from the main thread to the worker thread holds the +// job entries that will be performed on the worker. The other queue holds messages +// from the worker to the main thread. These messages include status updates, +// finishing operation and arbitrary functiors that need to be performed +// on the main thread during the jobs execution, like displaying intermediate +// results. +class BoostThreadWorker : public Worker, private Job::Ctl +{ + struct JobEntry // Goes into worker and also out of worker as a finalize msg + { + std::unique_ptr job; + bool canceled = false; + std::exception_ptr eptr = nullptr; + }; + + // A message data for status updates. Only goes from worker to main thread. + struct StatusInfo { int status; std::string msg; }; + + // An arbitrary callback to be called on the main thread. Only from worker + // to main thread. + struct MainThreadCallData + { + std::function fn; + std::promise promise; + }; + + struct EmptyMessage {}; + + class WorkerMessage + { + public: + enum MsgType { Empty, Status, Finalize, MainThreadCall }; + + private: + boost::variant m_data; + + public: + WorkerMessage() = default; + WorkerMessage(int s, std::string txt) + : m_data{StatusInfo{s, std::move(txt)}} + {} + WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {} + WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {} + + int get_type () const { return m_data.which(); } + + void deliver(BoostThreadWorker &runner); + }; + + using JobQueue = ThreadSafeQueueSPSC; + using MessageQueue = ThreadSafeQueueSPSC; + + boost::thread m_thread; + std::atomic m_running{false}, m_canceled{false}; + std::shared_ptr m_progress; + JobQueue m_input_queue; // from main thread to worker + MessageQueue m_output_queue; // form worker to main thread + std::string m_name; + + void run(); + + bool join(int timeout_ms = 0); + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override; + + bool was_canceled() const override { return m_canceled.load(); } + + std::future call_on_main_thread(std::function fn) override; + +public: + explicit BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes & attr, + const char * name = ""); + + explicit BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes && attr, + const char * name = "") + : BoostThreadWorker{std::move(pri), attr, name} + {} + + explicit BoostThreadWorker(std::shared_ptr pri, + const char * name = "") + : BoostThreadWorker{std::move(pri), {}, name} + {} + + ~BoostThreadWorker(); + + BoostThreadWorker(const BoostThreadWorker &) = delete; + BoostThreadWorker(BoostThreadWorker &&) = delete; + BoostThreadWorker &operator=(const BoostThreadWorker &) = delete; + BoostThreadWorker &operator=(BoostThreadWorker &&) = delete; + + bool push(std::unique_ptr job) override; + + bool is_idle() const override + { + // The assumption is that jobs can only be queued from a single main + // thread from which this method is also called. And the output + // messages are also processed only in this calling thread. In that + // case, if the input queue is empty, it will remain so during this + // function call. If the worker thread is also not running and the + // output queue is already processed, we can safely say that the + // worker is dormant. + return m_input_queue.empty() && !m_running.load() && m_output_queue.empty(); + } + + void cancel() override { m_canceled.store(true); } + void cancel_all() override { m_input_queue.clear(); cancel(); } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } + + void process_events() override; + bool wait_for_current_job(unsigned timeout_ms = 0) override; + bool wait_for_idle(unsigned timeout_ms = 0) override; + +}; + +}} // namespace Slic3r::GUI + +#endif // BOOSTTHREADWORKER_HPP diff --git a/src/slic3r/GUI/Jobs/BusyCursorJob.hpp b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp new file mode 100644 index 0000000000..530213b1d5 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp @@ -0,0 +1,48 @@ +#ifndef BUSYCURSORJOB_HPP +#define BUSYCURSORJOB_HPP + +#include "Job.hpp" + +#include + +namespace Slic3r { namespace GUI { + +struct CursorSetterRAII +{ + Job::Ctl &ctl; + CursorSetterRAII(Job::Ctl &c) : ctl{c} + { + ctl.call_on_main_thread([] { wxBeginBusyCursor(); }); + } + ~CursorSetterRAII() + { + ctl.call_on_main_thread([] { wxEndBusyCursor(); }); + } +}; + +template +class BusyCursored: public Job { + JobSubclass m_job; + +public: + template + BusyCursored(Args &&...args) : m_job{std::forward(args)...} + {} + + void process(Ctl &ctl) override + { + CursorSetterRAII cursor_setter{ctl}; + m_job.process(ctl); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + m_job.finalize(canceled, eptr); + } +}; + + +} +} + +#endif // BUSYCURSORJOB_HPP diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 870f31f2fc..c7d69eb500 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -3,6 +3,7 @@ #include "libslic3r/Model.hpp" #include "libslic3r/ClipperUtils.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" @@ -102,8 +103,12 @@ void FillBedJob::prepare() p.translation(X) -= p.bed_idx * stride; } -void FillBedJob::process() +void FillBedJob::process(Ctl &ctl) { + auto statustxt = _u8L("Filling bed"); + ctl.call_on_main_thread([this] { prepare(); }).wait(); + ctl.update_status(0, statustxt); + if (m_object_idx == -1 || m_selected.empty()) return; const GLCanvas3D::ArrangeSettings &settings = @@ -114,13 +119,13 @@ void FillBedJob::process() params.min_obj_distance = scaled(settings.distance); bool do_stop = false; - params.stopcondition = [this, &do_stop]() { - return was_canceled() || do_stop; + params.stopcondition = [&ctl, &do_stop]() { + return ctl.was_canceled() || do_stop; }; - params.progressind = [this](unsigned st) { + params.progressind = [this, &ctl, &statustxt](unsigned st) { if (st > 0) - update_status(int(m_status_range - st), _(L("Filling bed"))); + ctl.update_status(int(m_status_range - st) * 100 / status_range(), statustxt); }; params.on_packed = [&do_stop] (const ArrangePolygon &ap) { @@ -130,15 +135,18 @@ void FillBedJob::process() arrangement::arrange(m_selected, m_unselected, m_bedpts, params); // finalize just here. - update_status(m_status_range, was_canceled() ? - _(L("Bed filling canceled.")) : - _(L("Bed filling done."))); + ctl.update_status(100, ctl.was_canceled() ? + _u8L("Bed filling canceled.") : + _u8L("Bed filling done.")); } -void FillBedJob::finalize() +FillBedJob::FillBedJob() : m_plater{wxGetApp().plater()} {} + +void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) { // Ignore the arrange result if aborted. - if (was_canceled()) return; + if (canceled || eptr) + return; if (m_object_idx == -1) return; @@ -167,8 +175,6 @@ void FillBedJob::finalize() m_plater->sidebar() .obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt)); } - - Job::finalize(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index bf407656d1..b1417bbbd4 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -7,9 +7,9 @@ namespace Slic3r { namespace GUI { class Plater; -class FillBedJob : public PlaterJob +class FillBedJob : public Job { - int m_object_idx = -1; + int m_object_idx = -1; using ArrangePolygon = arrangement::ArrangePolygon; using ArrangePolygons = arrangement::ArrangePolygons; @@ -20,23 +20,20 @@ class FillBedJob : public PlaterJob Points m_bedpts; int m_status_range = 0; - -protected: - - void prepare() override; - void process() override; + Plater *m_plater; public: - FillBedJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + void prepare(); + void process(Ctl &ctl) override; - int status_range() const override + FillBedJob(); + + int status_range() const /*override*/ { return m_status_range; } - void finalize() override; + void finalize(bool canceled, std::exception_ptr &e) override; }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp deleted file mode 100644 index 9d0d4bc80c..0000000000 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include -#include - -#include "Job.hpp" -#include -#include - -namespace Slic3r { - -void GUI::Job::run(std::exception_ptr &eptr) -{ - m_running.store(true); - try { - process(); - } catch (...) { - eptr = std::current_exception(); - } - - m_running.store(false); - - // ensure to call the last status to finalize the job - update_status(status_range(), ""); -} - -void GUI::Job::update_status(int st, const wxString &msg) -{ - auto evt = new wxThreadEvent(wxEVT_THREAD, m_thread_evt_id); - evt->SetInt(st); - evt->SetString(msg); - wxQueueEvent(this, evt); -} - -GUI::Job::Job(std::shared_ptr pri) - : m_progress(std::move(pri)) -{ - m_thread_evt_id = wxNewId(); - - Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { - if (m_finalizing) return; - - auto msg = evt.GetString(); - if (!msg.empty() && !m_worker_error) - m_progress->set_status_text(msg.ToUTF8().data()); - - if (m_finalized) return; - - m_progress->set_progress(evt.GetInt()); - if (evt.GetInt() == status_range() || m_worker_error) { - // set back the original range and cancel callback - m_progress->set_range(m_range); - // Make sure progress indicators get the last value of their range - // to make sure they close, fade out, whathever - m_progress->set_progress(m_range); - m_progress->set_cancel_callback(); - wxEndBusyCursor(); - - if (m_worker_error) { - m_finalized = true; - m_progress->set_status_text(""); - m_progress->set_progress(m_range); - on_exception(m_worker_error); - } - else { - // This is an RAII solution to remember that finalization is - // running. The run method calls update_status(status_range(), "") - // at the end, which queues up a call to this handler in all cases. - // If process also calls update_status with maxed out status arg - // it will call this handler twice. It is not a problem unless - // yield is called inside the finilize() method, which would - // jump out of finalize and call this handler again. - struct Finalizing { - bool &flag; - Finalizing (bool &f): flag(f) { flag = true; } - ~Finalizing() { flag = false; } - } fin(m_finalizing); - - finalize(); - } - - // dont do finalization again for the same process - m_finalized = true; - } - }, m_thread_evt_id); -} - -void GUI::Job::start() -{ // Start the job. No effect if the job is already running - if (!m_running.load()) { - prepare(); - - // Save the current status indicatior range and push the new one - m_range = m_progress->get_range(); - m_progress->set_range(status_range()); - - // init cancellation flag and set the cancel callback - m_canceled.store(false); - m_progress->set_cancel_callback( - [this]() { m_canceled.store(true); }); - - m_finalized = false; - m_finalizing = false; - - // Changing cursor to busy - wxBeginBusyCursor(); - - try { // Execute the job - m_worker_error = nullptr; - m_thread = create_thread([this] { this->run(m_worker_error); }); - } catch (std::exception &) { - update_status(status_range(), - _(L("ERROR: not enough resources to " - "execute a new job."))); - } - - // The state changes will be undone when the process hits the - // last status value, in the status update handler (see ctor) - } -} - -bool GUI::Job::join(int timeout_ms) -{ - if (!m_thread.joinable()) return true; - - if (timeout_ms <= 0) - m_thread.join(); - else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) - return false; - - return true; -} - -void GUI::ExclusiveJobGroup::start(size_t jid) { - assert(jid < m_jobs.size()); - stop_all(); - m_jobs[jid]->start(); -} - -void GUI::ExclusiveJobGroup::join_all(int wait_ms) -{ - std::vector aborted(m_jobs.size(), false); - - for (size_t jid = 0; jid < m_jobs.size(); ++jid) - aborted[jid] = m_jobs[jid]->join(wait_ms); - - if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; })) - BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; -} - -bool GUI::ExclusiveJobGroup::is_any_running() const -{ - return std::any_of(m_jobs.begin(), m_jobs.end(), - [](const std::unique_ptr &j) { - return j->is_running(); - }); -} - -} - diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index 8243ce9430..824c0b8303 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -3,119 +3,53 @@ #include #include +#include #include "libslic3r/libslic3r.h" - -#include - #include "ProgressIndicator.hpp" -#include - -#include - namespace Slic3r { namespace GUI { -// A class to handle UI jobs like arranging and optimizing rotation. -// These are not instant jobs, the user has to be informed about their -// state in the status progress indicator. On the other hand they are -// separated from the background slicing process. Ideally, these jobs should -// run when the background process is not running. -// -// TODO: A mechanism would be useful for blocking the plater interactions: -// objects would be frozen for the user. In case of arrange, an animation -// could be shown, or with the optimize orientations, partial results -// could be displayed. -class Job : public wxEvtHandler -{ - int m_range = 100; - int m_thread_evt_id = wxID_ANY; - boost::thread m_thread; - std::atomic m_running{false}, m_canceled{false}; - bool m_finalized = false, m_finalizing = false; - std::shared_ptr m_progress; - std::exception_ptr m_worker_error = nullptr; - - void run(std::exception_ptr &); - -protected: - // status range for a particular job - virtual int status_range() const { return 100; } - - // status update, to be used from the work thread (process() method) - void update_status(int st, const wxString &msg = ""); +// A class representing a job that is to be run in the background, not blocking +// the main thread. Running it is up to a Worker object (see Worker interface) +class Job { +public: - bool was_canceled() const { return m_canceled.load(); } + // A controller interface that informs the job about cancellation and + // makes it possible for the job to advertise its status. + class Ctl { + public: + virtual ~Ctl() = default; - // Launched just before start(), a job can use it to prepare internals - virtual void prepare() {} + // status update, to be used from the work thread (process() method) + virtual void update_status(int st, const std::string &msg = "") = 0; - // The method where the actual work of the job should be defined. - virtual void process() = 0; - - // Launched when the job is finished. It refreshes the 3Dscene by def. - virtual void finalize() { m_finalized = true; } + // Returns true if the job was asked to cancel itself. + virtual bool was_canceled() const = 0; + // Execute a functor on the main thread. Note that the exact time of + // execution is hard to determine. This can be used to make modifications + // on the UI, like displaying some intermediate results or modify the + // cursor. + // This function returns a std::future object which enables the + // caller to optionally wait for the main thread to finish the function call. + virtual std::future call_on_main_thread(std::function fn) = 0; + }; + + virtual ~Job() = default; + + // The method where the actual work of the job should be defined. This is + // run on the worker thread. + virtual void process(Ctl &ctl) = 0; + + // Launched when the job is finished on the UI thread. + // If the job was cancelled, the first parameter will have a true value. // Exceptions occuring in process() are redirected from the worker thread - // into the main (UI) thread. This method is called from the main thread and - // can be overriden to handle these exceptions. - virtual void on_exception(const std::exception_ptr &eptr) - { - if (eptr) std::rethrow_exception(eptr); - } - -public: - Job(std::shared_ptr pri); - - bool is_finalized() const { return m_finalized; } - - Job(const Job &) = delete; - Job(Job &&) = delete; - Job &operator=(const Job &) = delete; - Job &operator=(Job &&) = delete; - - void start(); - - // To wait for the running job and join the threads. False is - // returned if the timeout has been reached and the job is still - // running. Call cancel() before this fn if you want to explicitly - // end the job. - bool join(int timeout_ms = 0); - - bool is_running() const { return m_running.load(); } - void cancel() { m_canceled.store(true); } -}; - -// Jobs defined inside the group class will be managed so that only one can -// run at a time. Also, the background process will be stopped if a job is -// started. -class ExclusiveJobGroup -{ - static const int ABORT_WAIT_MAX_MS = 10000; - - std::vector> m_jobs; - -protected: - virtual void before_start() {} - -public: - virtual ~ExclusiveJobGroup() = default; - - size_t add_job(std::unique_ptr &&job) - { - m_jobs.emplace_back(std::move(job)); - return m_jobs.size() - 1; - } - - void start(size_t jid); - - void cancel_all() { for (auto& j : m_jobs) j->cancel(); } - - void join_all(int wait_ms = 0); - - void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } - - bool is_any_running() const; + // into the main (UI) thread. This method receives the exception and can + // handle it properly. Assign nullptr to this second argument before + // function return to prevent further action. Leaving it with a non-null + // value will result in rethrowing by the worker. + virtual void finalize(bool /*canceled*/, std::exception_ptr &) {} }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp index cb71705687..f398f73339 100644 --- a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp @@ -12,11 +12,15 @@ void NotificationProgressIndicator::set_range(int range) void NotificationProgressIndicator::set_cancel_callback(CancelFn fn) { - m_nm->progress_indicator_set_cancel_callback(std::move(fn)); + m_cancelfn = std::move(fn); + m_nm->progress_indicator_set_cancel_callback(m_cancelfn); } void NotificationProgressIndicator::set_progress(int pr) { + if (!pr) + set_cancel_callback(m_cancelfn); + m_nm->progress_indicator_set_progress(pr); } diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp index 6b03af69df..b31cb7f7cd 100644 --- a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp @@ -9,7 +9,7 @@ class NotificationManager; class NotificationProgressIndicator: public ProgressIndicator { NotificationManager *m_nm = nullptr; - + CancelFn m_cancelfn; public: explicit NotificationProgressIndicator(NotificationManager *nm); diff --git a/src/slic3r/GUI/Jobs/PlaterJob.cpp b/src/slic3r/GUI/Jobs/PlaterJob.cpp deleted file mode 100644 index 4af205d41b..0000000000 --- a/src/slic3r/GUI/Jobs/PlaterJob.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "PlaterJob.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/Plater.hpp" - -namespace Slic3r { namespace GUI { - -void PlaterJob::on_exception(const std::exception_ptr &eptr) -{ - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (std::exception &e) { - show_error(m_plater, _(L("An unexpected error occured")) + ": "+ e.what()); - } -} - -}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/PlaterJob.hpp b/src/slic3r/GUI/Jobs/PlaterJob.hpp deleted file mode 100644 index fcf0a54b80..0000000000 --- a/src/slic3r/GUI/Jobs/PlaterJob.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PLATERJOB_HPP -#define PLATERJOB_HPP - -#include "Job.hpp" - -namespace Slic3r { namespace GUI { - -class Plater; - -class PlaterJob : public Job { -protected: - Plater *m_plater; - - void on_exception(const std::exception_ptr &) override; - -public: - - PlaterJob(std::shared_ptr pri, Plater *plater): - Job{std::move(pri)}, m_plater{plater} {} -}; - -}} // namespace Slic3r::GUI - -#endif // PLATERJOB_HPP diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp new file mode 100644 index 0000000000..5735902728 --- /dev/null +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -0,0 +1,127 @@ +#ifndef PLATERWORKER_HPP +#define PLATERWORKER_HPP + +#include + +#include "Worker.hpp" +#include "BusyCursorJob.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +template +class PlaterWorker: public Worker { + WorkerSubclass m_w; + Plater *m_plater; + + class PlaterJob : public Job { + std::unique_ptr m_job; + Plater *m_plater; + + public: + void process(Ctl &c) override + { + // Ensure that wxWidgets processing wakes up to handle outgoing + // messages in plater's wxIdle handler. Otherwise it might happen + // that the message will only be processed when an event like mouse + // move comes along which might be too late. + struct WakeUpCtl: Ctl { + Ctl &ctl; + WakeUpCtl(Ctl &c) : ctl{c} {} + + void update_status(int st, const std::string &msg = "") override + { + wxWakeUpIdle(); + ctl.update_status(st, msg); + } + + bool was_canceled() const override { return ctl.was_canceled(); } + + std::future call_on_main_thread(std::function fn) override + { + wxWakeUpIdle(); + return ctl.call_on_main_thread(std::move(fn)); + } + + } wctl{c}; + + CursorSetterRAII busycursor{wctl}; + m_job->process(wctl); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + m_job->finalize(canceled, eptr); + + if (eptr) try { + std::rethrow_exception(eptr); + } catch (std::exception &e) { + show_error(m_plater, _L("An unexpected error occured: ") + e.what()); + eptr = nullptr; + } + } + + PlaterJob(Plater *p, std::unique_ptr j) + : m_job{std::move(j)}, m_plater{p} + { + // TODO: decide if disabling slice button during UI job is what we + // want. + // if (m_plater) + // m_plater->sidebar().enable_buttons(false); + } + + ~PlaterJob() override + { + // TODO: decide if disabling slice button during UI job is what we want. + + // Reload scene ensures that the slice button gets properly + // enabled or disabled after the job finishes, depending on the + // state of slicing. This might be an overkill but works for now. + // if (m_plater) + // m_plater->canvas3D()->reload_scene(false); + } + }; + +public: + + template + PlaterWorker(Plater *plater, WorkerArgs &&...args) + : m_w{std::forward(args)...}, m_plater{plater} + { + // Ensure that messages from the worker thread to the UI thread are + // processed continuously. + plater->Bind(wxEVT_IDLE, [this](wxIdleEvent &) { + process_events(); + }); + } + + // Always package the job argument into a PlaterJob + bool push(std::unique_ptr job) override + { + return m_w.push(std::make_unique(m_plater, std::move(job))); + } + + bool is_idle() const override { return m_w.is_idle(); } + void cancel() override { m_w.cancel(); } + void cancel_all() override { m_w.cancel_all(); } + void process_events() override { m_w.process_events(); } + bool wait_for_current_job(unsigned timeout_ms = 0) override + { + return m_w.wait_for_current_job(timeout_ms); + } + bool wait_for_idle(unsigned timeout_ms = 0) override + { + return m_w.wait_for_idle(timeout_ms); + } +}; + +}} // namespace Slic3r::GUI + +#endif // PLATERJOB_HPP diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 95821a674d..e88d24fcdc 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -12,6 +12,8 @@ #include "slic3r/GUI/GUI_App.hpp" #include "libslic3r/AppConfig.hpp" +#include + namespace Slic3r { namespace GUI { void RotoptimizeJob::prepare() @@ -45,20 +47,23 @@ void RotoptimizeJob::prepare() } } -void RotoptimizeJob::process() +void RotoptimizeJob::process(Ctl &ctl) { int prev_status = 0; + auto statustxt = _u8L("Searching for optimal orientation"); + ctl.update_status(0, statustxt); + auto params = sla::RotOptimizeParams{} .accuracy(m_accuracy) .print_config(&m_default_print_cfg) - .statucb([this, &prev_status](int s) + .statucb([this, &prev_status, &ctl, &statustxt](int s) { if (s > 0 && s < 100) - update_status(prev_status + s / m_selected_object_ids.size(), - _(L("Searching for optimal orientation"))); + ctl.update_status(prev_status + s / m_selected_object_ids.size(), + statustxt); - return !was_canceled(); + return !ctl.was_canceled(); }); @@ -71,16 +76,20 @@ void RotoptimizeJob::process() prev_status += 100 / m_selected_object_ids.size(); - if (was_canceled()) break; + if (ctl.was_canceled()) break; } - update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : - _(L("Orientation found."))); + ctl.update_status(100, ctl.was_canceled() ? + _u8L("Orientation search canceled.") : + _u8L("Orientation found.")); } -void RotoptimizeJob::finalize() +RotoptimizeJob::RotoptimizeJob() : m_plater{wxGetApp().plater()} { prepare(); } + +void RotoptimizeJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (was_canceled()) return; + if (canceled || eptr) + return; for (const ObjRot &objrot : m_selected_object_ids) { ModelObject *o = m_plater->model().objects[size_t(objrot.idx)]; @@ -111,10 +120,8 @@ void RotoptimizeJob::finalize() // m_plater->find_new_position(o->instances); } - if (!was_canceled()) + if (!canceled) m_plater->update(); - - Job::finalize(); } }} diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index cdb367f23a..71a28deb7c 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -1,16 +1,17 @@ #ifndef ROTOPTIMIZEJOB_HPP #define ROTOPTIMIZEJOB_HPP -#include "PlaterJob.hpp" +#include "Job.hpp" #include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/I18N.hpp" -namespace Slic3r { +namespace Slic3r { namespace GUI { -namespace GUI { +class Plater; -class RotoptimizeJob : public PlaterJob +class RotoptimizeJob : public Job { using FindFn = std::function; @@ -44,19 +45,16 @@ class RotoptimizeJob : public PlaterJob }; std::vector m_selected_object_ids; - -protected: - - void prepare() override; - void process() override; + Plater *m_plater; public: - RotoptimizeJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} - {} + void prepare(); + void process(Ctl &ctl) override; - void finalize() override; + RotoptimizeJob(); + + void finalize(bool canceled, std::exception_ptr &) override; static constexpr size_t get_methods_count() { return std::size(Methods); } diff --git a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp new file mode 100644 index 0000000000..7dbecff2ae --- /dev/null +++ b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp @@ -0,0 +1,114 @@ +#ifndef SLAIMPORTDIALOG_HPP +#define SLAIMPORTDIALOG_HPP + +#include "SLAImportJob.hpp" + +#include +#include +#include +#include +#include + +#include "libslic3r/AppConfig.hpp" +#include "slic3r/GUI/I18N.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" + +//#include "libslic3r/Model.hpp" +//#include "libslic3r/PresetBundle.hpp" + +namespace Slic3r { namespace GUI { + +class SLAImportDialog: public wxDialog, public SLAImportJobView { + wxFilePickerCtrl *m_filepicker; + wxComboBox *m_import_dropdown, *m_quality_dropdown; + +public: + SLAImportDialog(Plater *plater) + : wxDialog{plater, wxID_ANY, "Import SLA archive"} + { + auto szvert = new wxBoxSizer{wxVERTICAL}; + auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; + + m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, + from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), + "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", + wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); + szfilepck->Add(m_filepicker, 1); + szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); + + auto szchoices = new wxBoxSizer{wxHORIZONTAL}; + + static const std::vector inp_choices = { + _(L("Import model and profile")), + _(L("Import profile only")), + _(L("Import model only")) + }; + + m_import_dropdown = new wxComboBox( + this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, + inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + + szchoices->Add(m_import_dropdown); + szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5); + + static const std::vector qual_choices = { + _(L("Accurate")), + _(L("Balanced")), + _(L("Quick")) + }; + + m_quality_dropdown = new wxComboBox( + this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, + qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + szchoices->Add(m_quality_dropdown); + + m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { + if (get_selection() == Sel::profileOnly) + m_quality_dropdown->Disable(); + else m_quality_dropdown->Enable(); + }); + + szvert->Add(szchoices, 0, wxALL, 5); + szvert->AddStretchSpacer(1); + auto szbtn = new wxBoxSizer(wxHORIZONTAL); + szbtn->Add(new wxButton{this, wxID_CANCEL}); + szbtn->Add(new wxButton{this, wxID_OK}); + szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); + + SetSizerAndFit(szvert); + } + + Sel get_selection() const override + { + int sel = m_import_dropdown->GetSelection(); + return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); + } + + Vec2i get_marchsq_windowsize() const override + { + enum { Accurate, Balanced, Fast}; + + switch(m_quality_dropdown->GetSelection()) + { + case Fast: return {8, 8}; + case Balanced: return {4, 4}; + default: + case Accurate: + return {2, 2}; + } + } + + std::string get_path() const override + { + return m_filepicker->GetPath().ToUTF8().data(); + } +}; + +}} // namespace Slic3r::GUI + +#endif // SLAIMPORTDIALOG_HPP diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 0d42cec2d3..1bb8cdf6c6 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -3,7 +3,6 @@ #include "libslic3r/Format/SL1.hpp" #include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/NotificationManager.hpp" @@ -11,104 +10,10 @@ #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" -#include -#include -#include #include -#include namespace Slic3r { namespace GUI { -enum class Sel { modelAndProfile, profileOnly, modelOnly}; - -class ImportDlg: public wxDialog { - wxFilePickerCtrl *m_filepicker; - wxComboBox *m_import_dropdown, *m_quality_dropdown; - -public: - ImportDlg(Plater *plater) - : wxDialog{plater, wxID_ANY, "Import SLA archive"} - { - auto szvert = new wxBoxSizer{wxVERTICAL}; - auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; - - m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, - from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), - "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", - wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); - szfilepck->Add(m_filepicker, 1); - szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); - - auto szchoices = new wxBoxSizer{wxHORIZONTAL}; - - static const std::vector inp_choices = { - _(L("Import model and profile")), - _(L("Import profile only")), - _(L("Import model only")) - }; - - m_import_dropdown = new wxComboBox( - this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, - inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); - - szchoices->Add(m_import_dropdown); - szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5); - - static const std::vector qual_choices = { - _(L("Accurate")), - _(L("Balanced")), - _(L("Quick")) - }; - - m_quality_dropdown = new wxComboBox( - this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, - qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); - szchoices->Add(m_quality_dropdown); - - m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { - if (get_selection() == Sel::profileOnly) - m_quality_dropdown->Disable(); - else m_quality_dropdown->Enable(); - }); - - szvert->Add(szchoices, 0, wxALL, 5); - szvert->AddStretchSpacer(1); - auto szbtn = new wxBoxSizer(wxHORIZONTAL); - szbtn->Add(new wxButton{this, wxID_CANCEL}); - szbtn->Add(new wxButton{this, wxID_OK}); - szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); - - SetSizerAndFit(szvert); - } - - Sel get_selection() const - { - int sel = m_import_dropdown->GetSelection(); - return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); - } - - Vec2i get_marchsq_windowsize() const - { - enum { Accurate, Balanced, Fast}; - - switch(m_quality_dropdown->GetSelection()) - { - case Fast: return {8, 8}; - case Balanced: return {4, 4}; - default: - case Accurate: - return {2, 2}; - } - } - - wxString get_path() const - { - return m_filepicker->GetPath(); - } -}; - class SLAImportJob::priv { public: Plater *plater; @@ -122,23 +27,28 @@ public: std::string err; ConfigSubstitutions config_substitutions; - ImportDlg import_dlg; + const SLAImportJobView * import_dlg; - priv(Plater *plt) : plater{plt}, import_dlg{plt} {} + priv(Plater *plt, const SLAImportJobView *view) : plater{plt}, import_dlg{view} {} }; -SLAImportJob::SLAImportJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater}, p{std::make_unique(plater)} -{} +SLAImportJob::SLAImportJob(const SLAImportJobView *view) + : p{std::make_unique(wxGetApp().plater(), view)} +{ + prepare(); +} SLAImportJob::~SLAImportJob() = default; -void SLAImportJob::process() +void SLAImportJob::process(Ctl &ctl) { - auto progr = [this](int s) { + auto statustxt = _u8L("Importing SLA archive"); + ctl.update_status(0, statustxt); + + auto progr = [&ctl, &statustxt](int s) { if (s < 100) - update_status(int(s), _(L("Importing SLA archive"))); - return !was_canceled(); + ctl.update_status(int(s), statustxt); + return !ctl.was_canceled(); }; if (p->path.empty()) return; @@ -161,15 +71,15 @@ void SLAImportJob::process() p->err = ex.what(); } - update_status(100, was_canceled() ? _(L("Importing canceled.")) : - _(L("Importing done."))); + ctl.update_status(100, ctl.was_canceled() ? _u8L("Importing canceled.") : + _u8L("Importing done.")); } void SLAImportJob::reset() { p->sel = Sel::modelAndProfile; p->mesh = {}; - p->profile = m_plater->sla_print().full_print_config(); + p->profile = p->plater->sla_print().full_print_config(); p->win = {2, 2}; p->path.Clear(); } @@ -178,22 +88,19 @@ void SLAImportJob::prepare() { reset(); - if (p->import_dlg.ShowModal() == wxID_OK) { - auto path = p->import_dlg.get_path(); - auto nm = wxFileName(path); - p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); - p->sel = p->import_dlg.get_selection(); - p->win = p->import_dlg.get_marchsq_windowsize(); - p->config_substitutions.clear(); - } else { - p->path = ""; - } + auto path = p->import_dlg->get_path(); + auto nm = wxFileName(path); + p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); + p->sel = p->import_dlg->get_selection(); + p->win = p->import_dlg->get_marchsq_windowsize(); + p->config_substitutions.clear(); } -void SLAImportJob::finalize() +void SLAImportJob::finalize(bool canceled, std::exception_ptr &eptr) { // Ignore the arrange result if aborted. - if (was_canceled()) return; + if (canceled || eptr) + return; if (!p->err.empty()) { show_error(p->plater, p->err); @@ -204,7 +111,7 @@ void SLAImportJob::finalize() std::string name = wxFileName(p->path).GetName().ToUTF8().data(); if (p->profile.empty()) { - m_plater->get_notification_manager()->push_notification( + p->plater->get_notification_manager()->push_notification( NotificationType::CustomNotification, NotificationManager::NotificationLevel::WarningNotificationLevel, _L("The imported SLA archive did not contain any presets. " @@ -213,7 +120,7 @@ void SLAImportJob::finalize() if (p->sel != Sel::modelOnly) { if (p->profile.empty()) - p->profile = m_plater->sla_print().full_print_config(); + p->profile = p->plater->sla_print().full_print_config(); const ModelObjectPtrs& objects = p->plater->model().objects; for (auto object : objects) diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp index c2ca10ef69..b2aea8bf89 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp @@ -1,22 +1,37 @@ #ifndef SLAIMPORTJOB_HPP #define SLAIMPORTJOB_HPP -#include "PlaterJob.hpp" +#include "Job.hpp" + +#include "libslic3r/Point.hpp" namespace Slic3r { namespace GUI { -class SLAImportJob : public PlaterJob { +class SLAImportJobView { +public: + enum Sel { modelAndProfile, profileOnly, modelOnly}; + + virtual ~SLAImportJobView() = default; + + virtual Sel get_selection() const = 0; + virtual Vec2i get_marchsq_windowsize() const = 0; + virtual std::string get_path() const = 0; +}; + +class Plater; + +class SLAImportJob : public Job { class priv; std::unique_ptr p; - -protected: - void prepare() override; - void process() override; - void finalize() override; + using Sel = SLAImportJobView::Sel; public: - SLAImportJob(std::shared_ptr pri, Plater *plater); + void prepare(); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &) override; + + SLAImportJob(const SLAImportJobView *); ~SLAImportJob(); void reset(); diff --git a/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp new file mode 100644 index 0000000000..d400490137 --- /dev/null +++ b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp @@ -0,0 +1,123 @@ +#ifndef THREADSAFEQUEUE_HPP +#define THREADSAFEQUEUE_HPP + +#include +#include +#include +#include +#include + +namespace Slic3r { namespace GUI { + +// Helper structure for overloads of ThreadSafeQueueSPSC::consume_one() +// to block if the queue is empty. +struct BlockingWait +{ + // Timeout to wait for the arrival of new element into the queue. + unsigned timeout_ms = 0; + + // An optional atomic flag to set true if an incoming element gets + // consumed. The flag will be atomically set to true when popping the + // front of the queue. + std::atomic *pop_flag = nullptr; +}; + +// A thread safe queue for one producer and one consumer. +template class Container = std::deque, + class... ContainerArgs> +class ThreadSafeQueueSPSC +{ + std::queue> m_queue; + mutable std::mutex m_mutex; + std::condition_variable m_cond_var; +public: + + // Consume one element, block if the queue is empty. + template bool consume_one(const BlockingWait &blkw, Fn &&fn) + { + static_assert(!std::is_reference_v, ""); + static_assert(std::is_default_constructible_v, ""); + static_assert(std::is_move_assignable_v || std::is_copy_assignable_v, ""); + + T el; + { + std::unique_lock lk{m_mutex}; + + auto pred = [this]{ return !m_queue.empty(); }; + if (blkw.timeout_ms > 0) { + auto timeout = std::chrono::milliseconds(blkw.timeout_ms); + if (!m_cond_var.wait_for(lk, timeout, pred)) + return false; + } + else + m_cond_var.wait(lk, pred); + + if constexpr (std::is_move_assignable_v) + el = std::move(m_queue.front()); + else + el = m_queue.front(); + + m_queue.pop(); + + if (blkw.pop_flag) + // The optional flag is set before the lock us unlocked. + blkw.pop_flag->store(true); + } + + fn(el); + return true; + } + + // Consume one element, return true if consumed, false if queue was empty. + template bool consume_one(Fn &&fn) + { + T el; + { + std::unique_lock lk{m_mutex}; + if (!m_queue.empty()) { + if constexpr (std::is_move_assignable_v) + el = std::move(m_queue.front()); + else + el = m_queue.front(); + + m_queue.pop(); + } else + return false; + } + + fn(el); + + return true; + } + + // Push element into the queue. + template void push(TArgs&&...el) + { + std::lock_guard lk{m_mutex}; + m_queue.emplace(std::forward(el)...); + m_cond_var.notify_one(); + } + + bool empty() const + { + std::lock_guard lk{m_mutex}; + return m_queue.empty(); + } + + size_t size() const + { + std::lock_guard lk{m_mutex}; + return m_queue.size(); + } + + void clear() + { + std::lock_guard lk{m_mutex}; + while (!m_queue.empty()) m_queue.pop(); + } +}; + +}} // namespace Slic3r::GUI + +#endif // THREADSAFEQUEUE_HPP diff --git a/src/slic3r/GUI/Jobs/Worker.hpp b/src/slic3r/GUI/Jobs/Worker.hpp new file mode 100644 index 0000000000..0bc7bc0863 --- /dev/null +++ b/src/slic3r/GUI/Jobs/Worker.hpp @@ -0,0 +1,119 @@ +#ifndef PRUSALSICER_WORKER_HPP +#define PRUSALSICER_WORKER_HPP + +#include + +#include "Job.hpp" + +namespace Slic3r { namespace GUI { + +// An interface of a worker that runs jobs on a dedicated worker thread, one +// after the other. It is assumed that every method of this class is called +// from the same main thread. +class Worker { +public: + // Queue up a new job after the current one. This call does not block. + // Returns false if the job gets discarded. + virtual bool push(std::unique_ptr job) = 0; + + // Returns true if no job is running, the job queue is empty and no job + // message is left to be processed. This means that nothing is left to + // finalize or take care of in the main thread. + virtual bool is_idle() const = 0; + + // Ask the current job gracefully to cancel. This call is not blocking and + // the job may or may not cancel eventually, depending on its + // implementation. Note that it is not trivial to kill a thread forcefully + // and we don't need that. + virtual void cancel() = 0; + + // This method will delete the queued jobs and cancel the current one. + virtual void cancel_all() = 0; + + // Needs to be called continuously to process events (like status update + // or finalizing of jobs) in the main thread. This can be done e.g. in a + // wxIdle handler. + virtual void process_events() = 0; + + // Wait until the current job finishes. Timeout will only be considered + // if not zero. Returns false if timeout is reached but the job has not + // finished. + virtual bool wait_for_current_job(unsigned timeout_ms = 0) = 0; + + // Wait until the whole job queue finishes. Timeout will only be considered + // if not zero. Returns false only if timeout is reached but the worker has + // not reached the idle state. + virtual bool wait_for_idle(unsigned timeout_ms = 0) = 0; + + // The destructor shall properly close the worker thread. + virtual ~Worker() = default; +}; + +template constexpr bool IsProcessFn = std::is_invocable_v; +template constexpr bool IsFinishFn = std::is_invocable_v; + +// Helper function to use the worker with arbitrary functors. +template>, + class = std::enable_if_t> > +bool queue_job(Worker &w, ProcessFn fn, FinishFn finishfn) +{ + struct LambdaJob: Job { + ProcessFn fn; + FinishFn finishfn; + + LambdaJob(ProcessFn pfn, FinishFn ffn) + : fn{std::move(pfn)}, finishfn{std::move(ffn)} + {} + + void process(Ctl &ctl) override { fn(ctl); } + void finalize(bool canceled, std::exception_ptr &eptr) override + { + finishfn(canceled, eptr); + } + }; + + auto j = std::make_unique(std::move(fn), std::move(finishfn)); + return w.push(std::move(j)); +} + +template>> +bool queue_job(Worker &w, ProcessFn fn) +{ + return queue_job(w, std::move(fn), [](bool, std::exception_ptr &) {}); +} + +inline bool queue_job(Worker &w, std::unique_ptr j) +{ + return w.push(std::move(j)); +} + +// Replace the current job queue with a new job. The signature is the same +// as for queue_job(). This cancels all jobs and +// will not wait. The new job will begin after the queue cancels properly. +// Note that this can be called from the UI thread and will not block it if +// the jobs take longer to cancel. +template bool replace_job(Worker &w, Args&& ...args) +{ + w.cancel_all(); + return queue_job(w, std::forward(args)...); +} + +// Cancel the current job and wait for it to actually be stopped. +inline bool stop_current_job(Worker &w, unsigned timeout_ms = 0) +{ + w.cancel(); + return w.wait_for_current_job(timeout_ms); +} + +// Cancel all pending jobs including current one and wait until the worker +// becomes idle. +inline bool stop_queue(Worker &w, unsigned timeout_ms = 0) +{ + w.cancel_all(); + return w.wait_for_idle(timeout_ms); +} + +}} // namespace Slic3r::GUI + +#endif // WORKER_HPP diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index c330f7cc92..ba3f6675fd 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -223,7 +223,7 @@ void KBShortcutsDialog::fill_shortcuts() { "A", L("Horizontal slider - Move active thumb Left") }, { "D", L("Horizontal slider - Move active thumb Right") }, { "X", L("On/Off one layer mode of the vertical slider") }, - { "L", L("Show/Hide Legend and Estimated printing time") }, + { "L", L("Show/Hide legend") }, { "C", L("Show/Hide G-code window") }, }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index fe96136e6c..08d2f682b4 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -47,6 +47,7 @@ #include "GUI_ObjectList.hpp" #include "GalleryDialog.hpp" #include "NotificationManager.hpp" +#include "Preferences.hpp" #ifdef _WIN32 #include @@ -272,6 +273,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S if (m_plater != nullptr) { m_plater->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); m_plater->show_action_buttons(true); + + preferences_dialog = new PreferencesDialog(this); } } @@ -571,7 +574,7 @@ void MainFrame::shutdown() #endif // _WIN32 if (m_plater != nullptr) { - m_plater->stop_jobs(); + m_plater->get_ui_job_worker().cancel_all(); // Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC, // when closing the application using Command+Q, a mouse event is triggered after this lambda is completed, @@ -1208,7 +1211,7 @@ void MainFrame::init_menubar_as_editor() append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S Archive") + dots, _L("Load an SL1 / Sl1S archive"), [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, - [this](){return m_plater != nullptr && !m_plater->is_any_job_running(); }, this); + [this](){return m_plater != nullptr && m_plater->get_ui_job_worker().is_idle(); }, this); import_menu->AppendSeparator(); append_menu_item(import_menu, wxID_ANY, _L("Import &Config") + dots + "\tCtrl+L", _L("Load exported configuration file"), @@ -1236,11 +1239,11 @@ void MainFrame::init_menubar_as_editor() [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(true); }, "export_to_sd", nullptr, [this]() {return can_export_gcode_sd(); }, this); export_menu->AppendSeparator(); - append_menu_item(export_menu, wxID_ANY, _L("Export Plate as &STL") + dots, _L("Export current plate as STL"), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater", nullptr, + append_menu_item(export_menu, wxID_ANY, _L("Export Plate as &STL/OBJ") + dots, _L("Export current plate as STL/OBJ"), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl_obj(); }, "export_plater", nullptr, [this](){return can_export_model(); }, this); - append_menu_item(export_menu, wxID_ANY, _L("Export Plate as STL &Including Supports") + dots, _L("Export current plate as STL including supports"), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, "export_plater", nullptr, + append_menu_item(export_menu, wxID_ANY, _L("Export Plate as STL/OBJ &Including Supports") + dots, _L("Export current plate as STL/OBJ including supports"), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl_obj(true); }, "export_plater", nullptr, [this](){return can_export_supports(); }, this); // Deprecating AMF export. Let's wait for user feedback. // append_menu_item(export_menu, wxID_ANY, _L("Export Plate as &AMF") + dots, _L("Export current plate as AMF"), @@ -1427,6 +1430,11 @@ void MainFrame::init_menubar_as_editor() append_menu_check_item(viewMenu, wxID_ANY, _L("Show &Labels") + sep + "E", _L("Show object/instance labels in 3D scene"), [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this); +#if ENABLE_PREVIEW_LAYOUT + append_menu_check_item(viewMenu, wxID_ANY, _L("Show Legen&d") + sep + "L", _L("Show legend in preview"), + [this](wxCommandEvent&) { m_plater->show_legend(!m_plater->is_legend_shown()); }, this, + [this]() { return m_plater->is_preview_shown(); }, [this]() { return m_plater->is_legend_shown(); }, this); +#endif // ENABLE_PREVIEW_LAYOUT append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse Sidebar") + sep + "Shift+" + sep_space + "Tab", _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); @@ -1544,6 +1552,12 @@ void MainFrame::init_menubar_as_gcodeviewer() if (m_plater != nullptr) { viewMenu = new wxMenu(); add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this)); +#if ENABLE_PREVIEW_LAYOUT + viewMenu->AppendSeparator(); + append_menu_check_item(viewMenu, wxID_ANY, _L("Show legen&d") + sep + "L", _L("Show legend"), + [this](wxCommandEvent&) { m_plater->show_legend(!m_plater->is_legend_shown()); }, this, + [this]() { return m_plater->is_preview_shown(); }, [this]() { return m_plater->is_legend_shown(); }, this); +#endif // ENABLE_PREVIEW_LAYOUT } // helpmenu diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 951ed70a1d..f385ee8f85 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -32,6 +32,7 @@ class Tab; class PrintHostQueueDialog; class Plater; class MainFrame; +class PreferencesDialog; enum QuickSlice { @@ -203,6 +204,7 @@ public: DiffPresetDialog diff_dialog; wxWindow* m_plater_page{ nullptr }; // wxProgressDialog* m_progress_dialog { nullptr }; + PreferencesDialog* preferences_dialog { nullptr }; PrintHostQueueDialog* m_printhost_queue_dlg; // std::shared_ptr m_statusbar; diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index db4972a5d4..acdbd249c4 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -14,6 +14,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "format.hpp" #include "I18N.hpp" @@ -137,9 +138,9 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); wxFont monospace = wxGetApp().code_font(); wxColour text_clr = wxGetApp().get_label_clr_default(); - wxColour bgr_clr = parent->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + wxColour bgr_clr = parent->GetBackgroundColour(); + auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); const int font_size = font.GetPointSize(); int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size }; html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size); diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 83537fd54f..400db751aa 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -145,7 +145,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) int blinking_button_width = m_bmp_blinking_sz.GetWidth() + m_h_gap; if (line.widget) { - h_pos += blinking_button_width; + h_pos += (line.has_undo_ui() ? 3 : 1) * blinking_button_width; for (auto child : line.widget_sizer->GetChildren()) if (child->IsWindow()) @@ -257,22 +257,28 @@ void OG_CustomCtrl::OnMotion(wxMouseEvent& event) break; } - for (size_t opt_idx = 0; opt_idx < line.rects_undo_icon.size(); opt_idx++) + size_t undo_icons_cnt = line.rects_undo_icon.size(); + assert(line.rects_undo_icon.size() == line.rects_undo_to_sys_icon.size()); + + const std::vector