From 06b47d98fcb8db16fc0c0426f9593d8361032598 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 2 Aug 2021 12:16:03 +0200 Subject: [PATCH 01/26] Fixed build when tech ENABLE_GCODE_VIEWER_STATISTICS is enabled --- src/slic3r/GUI/GCodeViewer.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 0fd1930d5..090accb6a 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2379,8 +2379,12 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("uniform_color", color4); }; +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] +#else auto render_as_points = [zoom, point_size, near_plane_height, set_uniform_color] - (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { +#endif // ENABLE_GCODE_VIEWER_STATISTICS + (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS shader.set_uniform("use_fixed_screen_size", 1); #else @@ -2409,7 +2413,12 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; - auto render_as_lines = [light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_lines = [this, light_intensity, set_uniform_color] +#else + auto render_as_lines = [light_intensity, set_uniform_color] +#endif // ENABLE_GCODE_VIEWER_STATISTICS + (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { shader.set_uniform("light_intensity", light_intensity); for (const RenderPath& path : buffer.render_paths) { if (path.ibuffer_id == ibuffer_id) { @@ -2422,7 +2431,12 @@ void GCodeViewer::render_toolpaths() const } }; - auto render_as_triangles = [set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_triangles = [this, set_uniform_color] +#else + auto render_as_triangles = [set_uniform_color] +#endif // ENABLE_GCODE_VIEWER_STATISTICS +(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { for (const RenderPath& path : buffer.render_paths) { if (path.ibuffer_id == ibuffer_id) { set_uniform_color(path.color, shader); @@ -2495,7 +2509,12 @@ void GCodeViewer::render_toolpaths() const } } - auto render_sequential_range_cap = [set_uniform_color](const SequentialRangeCap& cap) { +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_sequential_range_cap = [this, set_uniform_color] +#else + auto render_sequential_range_cap = [set_uniform_color] +#endif // ENABLE_GCODE_VIEWER_STATISTICS + (const SequentialRangeCap& cap) { GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); if (shader != nullptr) { shader->start_using(); From ab9dfb79327c12bf636effe03d7225c0e2111c78 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 2 Aug 2021 14:40:13 +0200 Subject: [PATCH 02/26] Added a few missing glsafe() --- src/slic3r/GUI/3DBed.cpp | 6 +++--- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 1e85a10b6..d7fb937d0 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -499,10 +499,10 @@ void Bed3D::render_model() const if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.0); - ::glPushMatrix(); - ::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z()); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z())); model->render(); - ::glPopMatrix(); + glsafe(::glPopMatrix()); shader->stop_using(); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 425e11f73..115a675ac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -359,10 +359,10 @@ void ObjectClipper::render_cut() const clipper->set_plane(*m_clp); clipper->set_transformation(trafo); - ::glPushMatrix(); - ::glColor3f(1.0f, 0.37f, 0.0f); + glsafe(::glPushMatrix()); + glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); clipper->render_cut(); - ::glPopMatrix(); + glsafe(::glPopMatrix()); ++clipper_id; } @@ -472,10 +472,10 @@ void SupportsClipper::render_cut() const m_clipper->set_plane(*ocl->get_clipping_plane()); m_clipper->set_transformation(supports_trafo); - ::glPushMatrix(); - ::glColor3f(1.0f, 0.f, 0.37f); + glsafe(::glPushMatrix()); + glsafe(::glColor3f(1.0f, 0.f, 0.37f)); m_clipper->render_cut(); - ::glPopMatrix(); + glsafe(::glPopMatrix()); } From bad51cdb520919362100b4381b9bb03cd1830643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 2 Aug 2021 14:03:17 +0200 Subject: [PATCH 03/26] OSX specific: Fixed darker colors of objects inside multi-material gizmo on macOS running on Arm64 CPU. For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. Because of this, objects had darker colors inside the multi-material gizmo. Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. --- resources/shaders/gouraud.fs | 3 +++ src/libslic3r/Platform.cpp | 41 +++++++++++++++++++++++++++-- src/libslic3r/Platform.hpp | 6 +++++ src/slic3r/GUI/GLShadersManager.cpp | 15 +++++++++-- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 572b37c7f..2f2ca5fb5 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -64,6 +64,9 @@ void main() float world_normal_z_fs = world_normal_z; if (compute_triangle_normals_in_fs) { vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); +#ifdef FLIP_TRIANGLE_NORMALS + triangle_normal = -triangle_normal; +#endif // First transform the normal into camera space and normalize the result. eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal); diff --git a/src/libslic3r/Platform.cpp b/src/libslic3r/Platform.cpp index ae02c42b3..4aa72c95f 100644 --- a/src/libslic3r/Platform.cpp +++ b/src/libslic3r/Platform.cpp @@ -3,6 +3,12 @@ #include #include +#if defined(__APPLE__) +#include +#include +#include +#endif + namespace Slic3r { static auto s_platform = Platform::Uninitialized; @@ -16,8 +22,39 @@ void detect_platform() s_platform_flavor = PlatformFlavor::Generic; #elif defined(__APPLE__) BOOST_LOG_TRIVIAL(info) << "Platform: OSX"; - s_platform = Platform::OSX; - s_platform_flavor = PlatformFlavor::Generic; + s_platform = Platform::OSX; + s_platform_flavor = PlatformFlavor::GenericOSX; + { + cpu_type_t type = 0; + size_t size = sizeof(type); + if (sysctlbyname("hw.cputype", &type, &size, NULL, 0) == 0) { + type &= ~CPU_ARCH_MASK; + if (type == CPU_TYPE_X86) { + int proc_translated = 0; + size = sizeof(proc_translated); + // Detect if native CPU is really X86 or PrusaSlicer runs through Rosetta. + if (sysctlbyname("sysctl.proc_translated", &proc_translated, &size, NULL, 0) == -1) { + if (errno == ENOENT) { + // Native CPU is X86, and property sysctl.proc_translated doesn't exist. + s_platform_flavor = PlatformFlavor::OSXOnX86; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnX86"; + } + } else if (proc_translated == 1) { + // Native CPU is ARM and PrusaSlicer runs through Rosetta. + s_platform_flavor = PlatformFlavor::OSXOnArm; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnArm"; + } else { + // Native CPU is X86. + s_platform_flavor = PlatformFlavor::OSXOnX86; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnX86"; + } + } else if (type == CPU_TYPE_ARM) { + // Native CPU is ARM + s_platform_flavor = PlatformFlavor::OSXOnArm; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnArm"; + } + } + } #elif defined(__linux__) BOOST_LOG_TRIVIAL(info) << "Platform: Linux"; s_platform = Platform::Linux; diff --git a/src/libslic3r/Platform.hpp b/src/libslic3r/Platform.hpp index 735728e89..1b4d74c02 100644 --- a/src/libslic3r/Platform.hpp +++ b/src/libslic3r/Platform.hpp @@ -28,6 +28,12 @@ enum class PlatformFlavor WSL2, // For Platform::BSDUnix OpenBSD, + // For Platform::OSX + GenericOSX, + // For Apple's on Intel X86 CPU + OSXOnX86, + // For Apple's on Arm CPU + OSXOnArm, }; // To be called on program start-up. diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 5ee14c526..788fe90c0 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -1,4 +1,5 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/Platform.hpp" #include "GLShadersManager.hpp" #include "3DScene.hpp" #include "GUI_App.hpp" @@ -43,9 +44,19 @@ std::pair GLShadersManager::init() // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } + // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. + // Because of this, objects had darker colors inside the multi-material gizmo. + // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. + if (platform_flavor() == PlatformFlavor::OSXOnArm) + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv #if ENABLE_ENVIRONMENT_MAP - , { "ENABLE_ENVIRONMENT_MAP"sv } + , "ENABLE_ENVIRONMENT_MAP"sv +#endif + }); + else + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } +#if ENABLE_ENVIRONMENT_MAP + , { "ENABLE_ENVIRONMENT_MAP"sv } #endif ); // used to render variable layers heights in 3d editor From e8e32795110ee65da9b54e6def97b89e8a4b3244 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 2 Aug 2021 14:59:36 +0200 Subject: [PATCH 04/26] Shapes Gallery : - Suppress to create a PNG-files for system shapes - Allow to load the OBJ files --- src/libslic3r/Utils.hpp | 4 +- src/libslic3r/utils.cpp | 8 ++-- src/slic3r/GUI/GUI_App.cpp | 1 + src/slic3r/GUI/GUI_App.hpp | 1 + src/slic3r/GUI/GalleryDialog.cpp | 79 +++++++++++++++++++++----------- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 81897553c..2ae726b97 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -100,8 +100,8 @@ extern bool is_ini_file(const boost::filesystem::directory_entry &path); extern bool is_idx_file(const boost::filesystem::directory_entry &path); extern bool is_gcode_file(const std::string &path); extern bool is_img_file(const std::string& path); -extern bool is_stl_file(const boost::filesystem::directory_entry& path); -extern bool is_stl_file(const std::string& path); +extern bool is_gallery_file(const boost::filesystem::directory_entry& path, char const* type); +extern bool is_gallery_file(const std::string& path, char const* type); extern bool is_shapes_dir(const std::string& dir); // File path / name / extension splitting utilities, working with UTF-8, diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 085db8705..3c2a0810b 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -766,14 +766,14 @@ bool is_img_file(const std::string &path) return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg"); } -bool is_stl_file(const boost::filesystem::directory_entry& dir_entry) +bool is_gallery_file(const boost::filesystem::directory_entry& dir_entry, char const* type) { - return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".stl") == 0; + return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), type) == 0; } -bool is_stl_file(const std::string &path) +bool is_gallery_file(const std::string &path, char const* type) { - return boost::iends_with(path, ".stl"); + return boost::iends_with(path, type); } bool is_shapes_dir(const std::string& dir) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4196dfdac..b08a2ef80 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -428,6 +428,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) /* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC", /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA", /* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF", + /* FT_GALLERY */ "Known files (*.stl, *.obj)|*.stl;*.STL;*.obj;*.OBJ", /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index fc62d4c34..be6c71f6c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -59,6 +59,7 @@ enum FileType FT_GCODE, FT_MODEL, FT_PROJECT, + FT_GALLERY, FT_INI, FT_SVG, diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index f306dff98..ad1834733 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -32,6 +32,7 @@ #include "libslic3r/AppConfig.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/Format/OBJ.hpp" #include "../Utils/MacDarkMode.hpp" namespace Slic3r { @@ -232,10 +233,11 @@ static std::string get_dir_path(bool sys_dir) #endif } -static void generate_thumbnail_from_stl(const std::string& filename) +static void generate_thumbnail_from_model(const std::string& filename) { - if (!boost::algorithm::iends_with(filename, ".stl")) { - BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_stl() [" << filename << "]"; + if (!boost::algorithm::iends_with(filename, ".stl") && + !boost::algorithm::iends_with(filename, ".obj")) { + BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_model() [" << filename << "]"; return; } @@ -244,7 +246,7 @@ static void generate_thumbnail_from_stl(const std::string& filename) model = Model::read_from_file(filename); } catch (std::exception&) { - BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_stl()"; + BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_model()"; return; } @@ -294,25 +296,28 @@ static void generate_thumbnail_from_stl(const std::string& filename) void GalleryDialog::load_label_icon_list() { // load names from files - auto add_files_from_gallery = [](std::vector& items, bool sys_dir, std::string& dir_path) + auto add_files_from_gallery = [](std::vector& items, bool is_sys_dir, std::string& dir_path) { - fs::path dir = get_dir(sys_dir); + fs::path dir = get_dir(is_sys_dir); if (!fs::exists(dir)) return; - dir_path = get_dir_path(sys_dir); + dir_path = get_dir_path(is_sys_dir); std::vector sorted_names; - for (auto& dir_entry : fs::directory_iterator(dir)) - if (TriangleMesh mesh; is_stl_file(dir_entry) && mesh.ReadSTLFile(dir_entry.path().string().c_str())) - sorted_names.push_back(dir_entry.path().stem().string()); + for (auto& dir_entry : fs::directory_iterator(dir)) { + TriangleMesh mesh; + if ((is_gallery_file(dir_entry, ".stl") && mesh.ReadSTLFile(dir_entry.path().string().c_str())) || + (is_gallery_file(dir_entry, ".obj") && load_obj(dir_entry.path().string().c_str(), &mesh) ) ) + sorted_names.push_back(dir_entry.path().filename().string()); + } // sort the filename case insensitive std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b) { return boost::algorithm::to_lower_copy(a) < boost::algorithm::to_lower_copy(b); }); for (const std::string& name : sorted_names) - items.push_back(Item{ name, sys_dir }); + items.push_back(Item{ name, is_sys_dir }); }; wxBusyCursor busy; @@ -330,10 +335,24 @@ void GalleryDialog::load_label_icon_list() std::string ext = ".png"; for (const auto& item : list_items) { - std::string img_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ext; - std::string stl_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ".stl"; - if (!fs::exists(img_name)) - generate_thumbnail_from_stl(stl_name); + fs::path model_path = fs::path((item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name); + std::string model_name = model_path.string(); + model_path.replace_extension("png"); + std::string img_name = model_path.string(); + +#ifdef 0 // use "1" just in DEBUG mode to the generation of the thumbnails for the sistem shapes + bool can_generate_thumbnail = true; +#else + bool can_generate_thumbnail = !item.is_system; +#endif //DEBUG + if (!fs::exists(img_name)) { + if (can_generate_thumbnail) + generate_thumbnail_from_model(model_name); + else { + add_default_image(m_image_list, item.is_system); + continue; + } + } wxImage image; if (!image.CanRead(from_u8(img_name)) || @@ -363,15 +382,15 @@ void GalleryDialog::load_label_icon_list() void GalleryDialog::get_input_files(wxArrayString& input_files) { for (const Item& item : m_selected_items) - input_files.Add(from_u8(get_dir_path(item.is_system) + item.name + ".stl")); + input_files.Add(from_u8(get_dir_path(item.is_system) + item.name)); } void GalleryDialog::add_custom_shapes(wxEvent& event) { wxArrayString input_files; - wxFileDialog dialog(this, _L("Choose one or more files (STL):"), + wxFileDialog dialog(this, _L("Choose one or more files (STL, OBJ):"), from_u8(wxGetApp().app_config->get_last_dir()), "", - file_wildcards(FT_STL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + file_wildcards(FT_GALLERY), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files); @@ -398,8 +417,10 @@ void GalleryDialog::del_custom_shapes(wxEvent& event) }; for (const Item& item : m_selected_items) { - remove_file(item.name + ".stl"); - remove_file(item.name + ".png"); + remove_file(item.name); + fs::path path = fs::path(item.name); + path.replace_extension("png"); + remove_file(path.string()); } update(); @@ -490,26 +511,32 @@ bool GalleryDialog::load_files(const wxArrayString& input_files) return false; } - // Iterate through the source directory + // Iterate through the input files for (size_t i = 0; i < input_files.size(); ++i) { std::string input_file = into_u8(input_files.Item(i)); - if (TriangleMesh mesh; !mesh.ReadSTLFile(input_file.c_str())) { + TriangleMesh mesh; + if (is_gallery_file(input_file, ".stl") && !mesh.ReadSTLFile(input_file.c_str())) { show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "STL"); continue; } + if (is_gallery_file(input_file, ".obj") && !load_obj(input_file.c_str(), &mesh)) { + show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "OBJ"); + continue; + } + try { fs::path current = fs::path(input_file); if (!fs::exists(dest_dir / current.filename())) fs::copy_file(current, dest_dir / current.filename()); else { - std::string filename = current.stem().string(); + std::string filename = current.filename().string(); int file_idx = 0; for (auto& dir_entry : fs::directory_iterator(dest_dir)) - if (is_stl_file(dir_entry)) { - std::string name = dir_entry.path().stem().string(); + if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) { + std::string name = dir_entry.path().filename().string(); if (filename == name) { if (file_idx == 0) file_idx++; @@ -524,7 +551,7 @@ bool GalleryDialog::load_files(const wxArrayString& input_files) file_idx = cur_idx+1; } if (file_idx > 0) { - filename += " (" + std::to_string(file_idx) + ").stl"; + filename += " (" + std::to_string(file_idx) + ")." + (is_gallery_file(input_file, ".stl") ? "stl" : "obj"); fs::copy_file(current, dest_dir / filename); } } From d86d11cc8ea976ed8a9c259cd45d3a04dce8ea6d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 2 Aug 2021 15:03:58 +0200 Subject: [PATCH 05/26] Shapes Gallery: Added PNG-files to the system gallery --- resources/shapes/OTHER_recycling_symbol.png | Bin 0 -> 14272 bytes resources/shapes/PETG_recycling_symbol.png | Bin 0 -> 13757 bytes resources/shapes/box.png | Bin 0 -> 4127 bytes resources/shapes/bunny.png | Bin 0 -> 15052 bytes resources/shapes/cylinder.png | Bin 0 -> 5091 bytes resources/shapes/pyramid.png | Bin 0 -> 3265 bytes resources/shapes/sphere.png | Bin 0 -> 13418 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/shapes/OTHER_recycling_symbol.png create mode 100644 resources/shapes/PETG_recycling_symbol.png create mode 100644 resources/shapes/box.png create mode 100644 resources/shapes/bunny.png create mode 100644 resources/shapes/cylinder.png create mode 100644 resources/shapes/pyramid.png create mode 100644 resources/shapes/sphere.png diff --git a/resources/shapes/OTHER_recycling_symbol.png b/resources/shapes/OTHER_recycling_symbol.png new file mode 100644 index 0000000000000000000000000000000000000000..7dde1945834fb33f489e19021e1d240c28729c8b GIT binary patch literal 14272 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 BG$jB4 literal 0 HcmV?d00001 diff --git a/resources/shapes/box.png b/resources/shapes/box.png new file mode 100644 index 0000000000000000000000000000000000000000..6df18fbc12093fe9a83298294079b3e3f435ebff GIT binary patch literal 4127 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

4&A` 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⁣qWqa1Or#?++P*(VszW;4l8U)zZIzj}2Yne# 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 literal 0 HcmV?d00001 From a26e9c46b2076c7bc2c85f6ff2f433868fa6d291 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 2 Aug 2021 15:08:54 +0200 Subject: [PATCH 06/26] Fixed build --- src/slic3r/GUI/GalleryDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index ad1834733..c4d46341b 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -340,7 +340,7 @@ void GalleryDialog::load_label_icon_list() model_path.replace_extension("png"); std::string img_name = model_path.string(); -#ifdef 0 // use "1" just in DEBUG mode to the generation of the thumbnails for the sistem shapes +#if 0 // use "1" just in DEBUG mode to the generation of the thumbnails for the sistem shapes bool can_generate_thumbnail = true; #else bool can_generate_thumbnail = !item.is_system; From 4f1a092ae09d7563c6b5d17366feadc5281678dd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 2 Aug 2021 17:28:08 +0200 Subject: [PATCH 07/26] PrintHostQueueDialog: MSW specific in DarkMode: Fixed font color for the selected Item --- src/slic3r/GUI/BonjourDialog.cpp | 1 + src/slic3r/GUI/ExtraRenderers.cpp | 33 +++++++++++++++++++++++++++++ src/slic3r/GUI/ExtraRenderers.hpp | 25 ++++++++++++++++++++++ src/slic3r/GUI/PrintHostDialogs.cpp | 26 +++++++++++++++++------ 4 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 65d5524c2..be0e20eb2 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -93,6 +93,7 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) }); Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this); + GUI::wxGetApp().UpdateDlgDarkUI(this); } BonjourDialog::~BonjourDialog() diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 9d6a19169..3fd6b9f04 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -335,3 +335,36 @@ bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& val } +// ---------------------------------------------------------------------------- +// TextRenderer +// ---------------------------------------------------------------------------- + +bool TextRenderer::SetValue(const wxVariant& value) +{ + m_value = value.GetString(); + return true; +} + +bool TextRenderer::GetValue(wxVariant& value) const +{ + return false; +} + +bool TextRenderer::Render(wxRect rect, wxDC* dc, int state) +{ +#ifdef _WIN32 + // workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color + RenderText(m_value, 0, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 : state); +#else + RenderText(m_value, 0, rect, dc, state); +#endif + + return true; +} + +wxSize TextRenderer::GetSize() const +{ + return GetTextExtent(m_value); +} + + diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp index ef8336d39..aebe52631 100644 --- a/src/slic3r/GUI/ExtraRenderers.hpp +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -161,4 +161,29 @@ private: }; + +// ---------------------------------------------------------------------------- +// TextRenderer +// ---------------------------------------------------------------------------- + +class TextRenderer : public wxDataViewCustomRenderer +{ +public: + TextRenderer(wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT + , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL + ) : wxDataViewCustomRenderer(wxT("string"), mode, align) {} + + bool SetValue(const wxVariant& value) override; + bool GetValue(wxVariant& value) const override; + + virtual bool Render(wxRect cell, wxDC* dc, int state) override; + virtual wxSize GetSize() const override; + + bool HasEditorCtrl() const override { return false; } + +private: + wxString m_value; +}; + + #endif // slic3r_GUI_ExtraRenderers_hpp_ diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 9b07157fd..3f2dc5a44 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -27,6 +27,7 @@ #include "MainFrame.hpp" #include "libslic3r/AppConfig.hpp" #include "NotificationManager.hpp" +#include "ExtraRenderers.hpp" namespace fs = boost::filesystem; @@ -214,14 +215,25 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) } job_list = new wxDataViewListCtrl(this, wxID_ANY); + + // MSW DarkMode: workaround for the selected item in the list + auto append_text_column = [this](const wxString& label, int width, wxAlignment align = wxALIGN_LEFT, + int flags = wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE) { +#ifdef _WIN32 + job_list->AppendColumn(new wxDataViewColumn(label, new TextRenderer(), job_list->GetColumnCount(), width, align, flags)); +#else + job_list->AppendTextColumn(label, wxDATAVIEW_CELL_INERT, width, align, flags); +#endif + }; + // Note: Keep these in sync with Column - job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT, widths[0], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT, widths[2], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT, widths[3], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), wxDATAVIEW_CELL_INERT, widths[4], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT, widths[5], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_L("Error Message"), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN); + append_text_column(_L("ID"), widths[0]); + job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); + append_text_column(_L("Status"),widths[2]); + append_text_column(_L("Host"), widths[3]); + append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]); + append_text_column(_L("Filename"), widths[5]); + append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN); auto *btnsizer = new wxBoxSizer(wxHORIZONTAL); btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected")); From 1b49dd98696bcd115785f862e9bde0b204cf5647 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 3 Aug 2021 09:25:54 +0200 Subject: [PATCH 08/26] PhysicalPrinterDialog: Remove all leading and trailing spaces from "print_host" input --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index f7593ba16..f189378a6 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -434,6 +434,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr e.Skip(); temp->GetToolTip()->Enable(true); #endif // __WXGTK__ + // Remove all leading and trailing spaces from the input + std::string trimed_str, str = trimed_str = temp->GetValue().ToStdString(); + boost::trim(trimed_str); + if (trimed_str != str) + temp->SetValue(trimed_str); + TextCtrl* field = dynamic_cast(printhost_field); if (field) field->propagate_value(); From 5bd14cf83aa2643c463ad3d596c280bcf0182690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 3 Aug 2021 10:16:52 +0200 Subject: [PATCH 09/26] Fixed the compiler warning. --- src/libslic3r/TriangleMeshSlicer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index ef90402a9..aa2763968 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -372,7 +372,7 @@ static inline IntersectionLines slice_make_lines( FaceFilter face_filter) { IntersectionLines lines; - for (int face_idx = 0; face_idx < mesh_faces.size(); ++ face_idx) + for (int face_idx = 0; face_idx < int(mesh_faces.size()); ++ face_idx) if (face_filter(face_idx)) { const Vec3i &indices = mesh_faces[face_idx]; stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) }; From 0092c448b8cde48da19d427d9cd4fc9c07f5f87f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 3 Aug 2021 11:28:31 +0200 Subject: [PATCH 10/26] OSX specific: GalleryDialog: Fixed scale of the default icon --- src/slic3r/GUI/GalleryDialog.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index c4d46341b..5429e2658 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -206,7 +206,11 @@ static void add_lock(wxImage& image) static void add_default_image(wxImageList* img_list, bool is_system) { - wxBitmap bmp = create_scaled_bitmap("cog", nullptr, IMG_PX_CNT, true); + int sz = IMG_PX_CNT; +#ifdef __APPLE__ + sz /= mac_max_scaling_factor(); +#endif + wxBitmap bmp = create_scaled_bitmap("cog", nullptr, sz, true); if (is_system) { wxImage image = bmp.ConvertToImage(); From d13c08837cb9db5ad9e4a5e2ccb943e236ee83ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 30 Jul 2021 16:11:23 +0200 Subject: [PATCH 11/26] Fixed an issue that some triangles weren't selected when bucket fill was used in the multi-material painting gizmo. --- src/libslic3r/TriangleSelector.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index ad823c55d..758b3ab4d 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -277,22 +277,22 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi, if (itriangle == -1) return; - auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx) -> void { + auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx, Partition partition) -> void { assert(subtriangle_idx == -1); if (!m_triangles[subtriangle_idx].is_split()) touching_subtriangles_out.emplace_back(subtriangle_idx); else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1) - append_touching_subtriangles(subtriangle_idx, vertexi, midpoint, touching_subtriangles_out); + append_touching_subtriangles(subtriangle_idx, partition == Partition::First ? vertexi : midpoint, partition == Partition::First ? midpoint : vertexj, touching_subtriangles_out); else append_touching_subtriangles(subtriangle_idx, vertexi, vertexj, touching_subtriangles_out); }; std::pair touching = this->triangle_subtriangles(itriangle, vertexi, vertexj); if (touching.first != -1) - process_subtriangle(touching.first); + process_subtriangle(touching.first, Partition::First); if (touching.second != -1) - process_subtriangle(touching.second); + process_subtriangle(touching.second, Partition::Second); } void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate) @@ -437,7 +437,7 @@ int TriangleSelector::neighbor_child(int itriangle, int vertexi, int vertexj, Pa std::pair TriangleSelector::triangle_subtriangles(int itriangle, int vertexi, int vertexj) const { - return itriangle == -1 ? std::make_pair(-1, -1) : this->triangle_subtriangles(m_triangles[itriangle], vertexi, vertexj); + return itriangle == -1 ? std::make_pair(-1, -1) : Slic3r::TriangleSelector::triangle_subtriangles(m_triangles[itriangle], vertexi, vertexj); } std::pair TriangleSelector::triangle_subtriangles(const Triangle &tr, int vertexi, int vertexj) From 93b86da770dd20a5d69c369fb9eff4b349e9fb1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 30 Jul 2021 16:47:03 +0200 Subject: [PATCH 12/26] Fixed the wrong threshold in the multi-material segmentation. --- src/libslic3r/MultiMaterialSegmentation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 65284ffac..8903ee0a2 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -696,9 +696,9 @@ struct MMU_Graph assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); } else if (bbox.contains(vertex_point)) { - if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < 3 * SCALED_EPSILON) { + if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) { vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx)); - } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= 3 * SCALED_EPSILON) { + } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(3 * SCALED_EPSILON)) { closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count())); vertex.color(this->nodes_count()); this->nodes.push_back({vertex_point}); From b16aada962454791790b04b0a4d87868990edfdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 30 Jul 2021 18:11:02 +0200 Subject: [PATCH 13/26] Added clipping of finite Voronoi edges that have coordinates that don't fit inside type coord_t. --- src/libslic3r/MultiMaterialSegmentation.cpp | 91 +++++++++++++-------- 1 file changed, 59 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 8903ee0a2..95475b0d7 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -5,6 +5,7 @@ #include "Print.hpp" #include "VoronoiVisualUtils.hpp" #include "MutablePolygon.hpp" +#include "format.hpp" #include #include @@ -502,11 +503,13 @@ static std::vector> colorize_polygons(const std::vector using boost::polygon::voronoi_diagram; -static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); } +static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return {coord_t(point->x()), coord_t(point->y())}; } -static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } +static inline Point mk_point(const Voronoi::Internal::point_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } -static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } +static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } + +static inline Vec2d mk_vec2(const voronoi_diagram::vertex_type *point) { return {point->x(), point->y()}; } struct MMU_Graph { @@ -796,6 +799,27 @@ static inline void init_polygon_indices(const MMU_Graph } } +// Voronoi edges produced by Voronoi generator cloud have coordinates that don't fit inside coord_t (int32_t). +// Because of that, this function tries to clip edges that have one endpoint of the edge inside the BoundingBox. +static inline Line clip_finite_voronoi_edge(const Voronoi::VD::edge_type &edge, const BoundingBoxf &bbox) +{ + assert(edge.is_finite()); + Vec2d v0 = mk_vec2(edge.vertex0()); + Vec2d v1 = mk_vec2(edge.vertex1()); + bool contains_v0 = bbox.contains(v0); + bool contains_v1 = bbox.contains(v1); + if ((contains_v0 && contains_v1) || (!contains_v0 && !contains_v1)) + return {mk_point(edge.vertex0()), mk_point(edge.vertex1())}; + + Vec2d vector = (v1 - v0).normalized() * bbox.size().norm(); + if (!contains_v0) + v0 = (v1 - vector); + else + v1 = (v0 + vector); + + return {v0.cast(), v1.cast()}; +} + static MMU_Graph build_graph(size_t layer_idx, const std::vector> &color_poly) { Geometry::VoronoiDiagram vd; @@ -852,7 +876,8 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector(), bbox.max.cast()); + const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); // Make a copy of the input segments with the double type. std::vector segments; @@ -890,72 +915,74 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vectoris_finite()) { - const Point v0 = mk_point(edge_it->vertex0()); - const Point v1 = mk_point(edge_it->vertex1()); - const size_t from_idx = edge_it->vertex0()->color(); - const size_t to_idx = edge_it->vertex1()->color(); - // Both points are on contour, so skip them. In cases of duplicate Voronoi vertices, skip edges between the same two points. - if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) continue; + if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) + continue; - const Line edge_line(v0, v1); + const Line edge_line = clip_finite_voronoi_edge(*edge_it, bbox_clip); const Line contour_line = lines_colored[edge_it->cell()->source_index()].line; const ColoredLine colored_line = lines_colored[edge_it->cell()->source_index()]; const ColoredLine contour_line_prev = get_prev_contour_line(edge_it); const ColoredLine contour_line_next = get_next_contour_line(edge_it); - Point intersection; if (edge_it->vertex0()->color() >= graph.nodes_count() || edge_it->vertex1()->color() >= graph.nodes_count()) { -// if(edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) { -// -// } - if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) { - Line contour_line_twin = lines_colored[edge_it->twin()->cell()->source_index()].line; + enum class Vertex { VERTEX0, VERTEX1 }; + auto append_edge_if_intersects_with_contour = [&graph, &lines_colored, &edge_line, &contour_line](const voronoi_diagram::const_edge_iterator &edge_iterator, const Vertex vertex) { + Point intersection; + Line contour_line_twin = lines_colored[edge_iterator->twin()->cell()->source_index()].line; if (line_intersection_with_epsilon(contour_line_twin, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->twin()->cell()->source_index()); + const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->twin()->cell()->source_index()); const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line_twin, intersection) ? graph_arc.from_idx : graph_arc.to_idx; - graph.append_edge(edge_it->vertex1()->color(), to_idx_l); + graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l); } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->cell()->source_index()); + const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->cell()->source_index()); const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line, intersection) ? graph_arc.from_idx : graph_arc.to_idx; - graph.append_edge(edge_it->vertex1()->color(), to_idx_l); + graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l); } - mark_processed(edge_it); - } + mark_processed(edge_iterator); + }; + + if (edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) + append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX0); + + if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) + append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX1); } else if (graph.is_edge_attach_to_contour(edge_it)) { mark_processed(edge_it); // Skip edges witch connection two points on a contour if (graph.is_edge_connecting_two_contour_vertices(edge_it)) continue; + const size_t from_idx = edge_it->vertex0()->color(); + const size_t to_idx = edge_it->vertex1()->color(); if (graph.is_vertex_on_contour(edge_it->vertex0())) { - if (is_point_closer_to_beginning_of_line(contour_line, v0)) { - if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, v1)) { + if (is_point_closer_to_beginning_of_line(contour_line, edge_line.a)) { + if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.b)) { graph.append_edge(from_idx, to_idx); force_edge_adding[colored_line.poly_idx] = false; } } else { - if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, v1)) { + if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.b)) { graph.append_edge(from_idx, to_idx); force_edge_adding[colored_line.poly_idx] = false; } } } else { assert(graph.is_vertex_on_contour(edge_it->vertex1())); - if (is_point_closer_to_beginning_of_line(contour_line, v1)) { - if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, v0)) { + if (is_point_closer_to_beginning_of_line(contour_line, edge_line.b)) { + if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.a)) { graph.append_edge(from_idx, to_idx); force_edge_adding[colored_line.poly_idx] = false; } } else { - if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, v0)) { + if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.a)) { graph.append_edge(from_idx, to_idx); force_edge_adding[colored_line.poly_idx] = false; } } } - } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { + } else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { mark_processed(edge_it); Point real_v0 = graph.nodes[edge_it->vertex0()->color()].point; Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point; @@ -1202,7 +1229,7 @@ static void cut_segmented_layers(const std::vector BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end"; } -// #define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM // Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo static inline std::vector> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object, @@ -1671,7 +1698,7 @@ static void export_regions_to_svg(const std::string &path, const std::vector ®ion : regions) { - int region_color = region.second; + int region_color = int(region.second); if (region_color >= 0 && region_color < int(colors.size())) svg.draw(region.first, colors[region_color]); else From 742a373c1f18320e9a173dff51adc52b1aecc7fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 29 Jul 2021 13:02:33 +0200 Subject: [PATCH 14/26] Added invalidation of the sliced object when gap-fill enabled/disabled if the object is painted using the multi-material painting gizmo. Filtering of unprintable regions in multi-material segmentation depends on if gap-fill is enabled or not. So sliced object is invalidated when gap-fill was enabled/disabled by option "gap_fill_enabled" or by changing "gap_fill_speed" to force recomputation of the multi-material segmentation. --- src/libslic3r/Model.cpp | 5 +++++ src/libslic3r/Model.hpp | 4 ++++ src/libslic3r/Print.hpp | 2 ++ src/libslic3r/PrintObject.cpp | 23 +++++++++++++++++++++-- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0b9614fa2..e6006ef58 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -725,6 +725,11 @@ void ModelObject::clear_volumes() this->invalidate_bounding_box(); } +bool ModelObject::is_mm_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); +} + void ModelObject::sort_volumes(bool full_sort) { // sort volumes inside the object to order "Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. " diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index fda500810..11dcfa775 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -285,6 +285,8 @@ public: void clear_volumes(); void sort_volumes(bool full_sort); bool is_multiparts() const { return volumes.size() > 1; } + // Checks if any of object volume is painted using the multi-material painting gizmo. + bool is_mm_painted() const; ModelInstance* add_instance(); ModelInstance* add_instance(const ModelInstance &instance); @@ -715,6 +717,8 @@ public: this->mmu_segmentation_facets.set_new_unique_id(); } + bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } + protected: friend class Print; friend class SLAPrint; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index ba631c3ee..1854800cc 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -317,6 +317,8 @@ public: bool has_support() const { return m_config.support_material || m_config.support_material_enforce_layers > 0; } bool has_raft() const { return m_config.raft_layers > 0; } bool has_support_material() const { return this->has_support() || this->has_raft(); } + // Checks if the model object is painted using the multi-material painting gizmo. + bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }; // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) std::vector object_extruders() const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 7aa39c823..8ebe1eb10 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -509,13 +509,32 @@ bool PrintObject::invalidate_state_by_config_options( } else if ( opt_key == "perimeters" || opt_key == "extra_perimeters" - || opt_key == "gap_fill_enabled" - || opt_key == "gap_fill_speed" || opt_key == "first_layer_extrusion_width" || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" || opt_key == "external_perimeters_first") { steps.emplace_back(posPerimeters); + } else if ( + opt_key == "gap_fill_enabled" + || opt_key == "gap_fill_speed") { + // Return true if gap-fill speed has changed from zero value to non-zero or from non-zero value to zero. + auto is_gap_fill_changed_state_due_to_speed = [&opt_key, &old_config, &new_config]() -> bool { + if (opt_key == "gap_fill_speed") { + const auto *old_gap_fill_speed = old_config.option(opt_key); + const auto *new_gap_fill_speed = new_config.option(opt_key); + assert(old_gap_fill_speed && new_gap_fill_speed); + return (old_gap_fill_speed->value > 0.f && new_gap_fill_speed->value == 0.f) || + (old_gap_fill_speed->value == 0.f && new_gap_fill_speed->value > 0.f); + } + return false; + }; + + // Filtering of unprintable regions in multi-material segmentation depends on if gap-fill is enabled or not. + // So step posSlice is invalidated when gap-fill was enabled/disabled by option "gap_fill_enabled" or by + // changing "gap_fill_speed" to force recomputation of the multi-material segmentation. + if (this->is_mm_painted() && (opt_key == "gap_fill_enabled" || (opt_key == "gap_fill_speed" && is_gap_fill_changed_state_due_to_speed()))) + steps.emplace_back(posSlice); + steps.emplace_back(posPerimeters); } else if ( opt_key == "layer_height" || opt_key == "first_layer_height" From 9e5fc2e71386b828aca7db7526568a0041d26dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 29 Jul 2021 13:05:28 +0200 Subject: [PATCH 15/26] XY size compensation is ignored when the object is also painted using the multi-material painting gizmo. A user is also notified about it. --- src/libslic3r/PrintApply.cpp | 5 +++-- src/libslic3r/PrintObjectSlice.cpp | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 577220423..0e252ac6f 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -812,7 +812,8 @@ static PrintObjectRegions* generate_print_object_regions( layer_ranges_regions.push_back({ range.layer_height_range, range.config }); } - update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, std::max(0.f, xy_size_compensation)); + const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); + update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation)); std::vector region_set; auto get_create_region = [®ion_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* { @@ -1313,7 +1314,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_default_region_config, model_object_status.print_instances.front().trafo, num_extruders, - float(print_object.config().xy_size_compensation.value), + print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value), painting_extruders); } for (auto it = it_print_object; it != it_print_object_end; ++it) diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 82fd04bce..818519be4 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -167,7 +167,8 @@ static std::vector slice_volumes_inner( params_base.mode_below = params_base.mode; - const auto extra_offset = std::max(0.f, float(print_object_config.xy_size_compensation.value)); + const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); + const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_size_compensation.value)); for (const ModelVolume *model_volume : model_volumes) if (model_volume_needs_slicing(*model_volume)) { @@ -725,6 +726,17 @@ void PrintObject::slice_volumes() // Is any ModelVolume MMU painted? if (const auto& volumes = this->model_object()->volumes; std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) { + + // If XY Size compensation is also enabled, notify the user that XY Size compensation + // would not be used because the object is multi-material painted. + if (m_config.xy_size_compensation.value != 0.f) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::CRITICAL, + L("An object has enabled XY Size compensation which will not be used because it is also multi-material painted.\nXY Size " + "compensation cannot be combined with multi-material painting.") + + "\n" + (L("Object name")) + ": " + this->model_object()->name); + } + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - MMU segmentation"; apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } @@ -733,8 +745,8 @@ void PrintObject::slice_volumes() BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; { // Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing. - const auto xy_compensation_scaled = scaled(std::min(m_config.xy_size_compensation.value, 0.)); - const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ? + const auto xy_compensation_scaled = this->is_mm_painted() ? scaled(0.f) : scaled(std::min(m_config.xy_size_compensation.value, 0.)); + const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ? // Only enable Elephant foot compensation if printing directly on the print bed. float(scale_(m_config.elefant_foot_compensation.value)) : 0.f; From 7d9cce1298b90c0cda2d9ae08bb30d5727bb75e8 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 4 Aug 2021 10:20:59 +0200 Subject: [PATCH 16/26] Do not open 'Did you know' notifications in gcodeviewer, they make no sense and make slicer crash sometimes --- src/slic3r/GUI/GUI_App.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b08a2ef80..9333efb20 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -663,7 +663,7 @@ void GUI_App::post_init() } // show "Did you know" notification - if (app_config->get("show_hints") == "1") + if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) plater_->get_notification_manager()->push_hint_notification(); // The extra CallAfter() is needed because of Mac, where this is the only way From d0d3c5ad164266dbaf48e58a00c92f8197a2fb18 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 4 Aug 2021 10:25:15 +0200 Subject: [PATCH 17/26] Fix of variable layer height mode opening from ObjectList: when the respective object info line was clicked, the variable layer height mode was opened correctly, but closing it through the toolbar deactivated most of the icons as if it was just opened. --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index be44d32a4..723c93da2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5250,7 +5250,7 @@ void Plater::convert_unit(ConversionType conv_type) void Plater::toggle_layers_editing(bool enable) { if (canvas3D()->is_layers_editing_enabled() != enable) - wxPostEvent(canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); + canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting")); } void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes) From bc81c22ea9327577f8f2fb558fb7bcadbb8eabda Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 4 Aug 2021 12:15:20 +0200 Subject: [PATCH 18/26] CLI: Ensure that objects are on bed by default, new CLI config option: 'dont-ensure-on-bed' (which allows to override). This was the original behaviour in Slic3r and Sli3rPE, probably broken long ago when CLI was ported from Perl. Also, --scale-to-fit should now work again (#5772) --- src/PrusaSlicer.cpp | 12 ++++++++++++ src/libslic3r/Model.cpp | 13 +++++-------- src/libslic3r/PrintConfig.cpp | 4 ++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index b3e6841c9..21b161456 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -258,6 +258,7 @@ int CLI::run(int argc, char **argv) Points bed = get_bed_shape(m_print_config); ArrangeParams arrange_cfg; arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config)); + bool user_ensure_on_bed = true; for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { @@ -330,6 +331,10 @@ int CLI::run(int argc, char **argv) } } else if (opt_key == "dont_arrange") { // do nothing - this option alters other transform options + } else if (opt_key == "dont_ensure_on_bed") { + // Remember that we saw this so we don't lift objects from the bed + // after the other transformations are processed. + user_ensure_on_bed = false; } else if (opt_key == "rotate") { for (auto &model : m_models) for (auto &o : model.objects) @@ -432,6 +437,13 @@ int CLI::run(int argc, char **argv) } } + // All transforms have been dealt with. Now ensure that the objects are on bed. + // (Unless the user said otherwise.) + if (user_ensure_on_bed) + for (auto &model : m_models) + for (auto &o : model.objects) + o->ensure_on_bed(); + // loop through action options for (auto const &opt_key : m_actions) { if (opt_key == "help") { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index e6006ef58..ba743f49c 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1745,18 +1745,15 @@ void ModelVolume::scale(const Vec3d& scaling_factors) void ModelObject::scale_to_fit(const Vec3d &size) { -/* - BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; Vec3d orig_size = this->bounding_box().size(); - float factor = fminf( - size.x / orig_size.x, - fminf( - size.y / orig_size.y, - size.z / orig_size.z + double factor = std::min( + size.x() / orig_size.x(), + std::min( + size.y() / orig_size.y(), + size.z() / orig_size.z() ) ); this->scale(factor); -*/ } void ModelVolume::assign_new_unique_ids_recursive() diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 512843110..16c16f038 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4154,6 +4154,10 @@ CLITransformConfigDef::CLITransformConfigDef() def->label = L("Don't arrange"); def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates."); + def = this->add("dont_ensure_on_bed", coBool); + def->label = L("Don't ensure on bed"); + def->tooltip = L("Do not lift the object above the bed when it is partially below."); + def = this->add("duplicate", coInt); def->label = L("Duplicate"); def->tooltip =L("Multiply copies by this factor."); From 7fd9a9cf6e50008aef0dd6f97b04615861b2ef0a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 4 Aug 2021 12:30:15 +0200 Subject: [PATCH 19/26] ObjectList: Fixed a crash during a print technology change when InfoItem is selected + Add "Gallery" menu Item for Advanced mode too --- src/slic3r/GUI/GUI_Factories.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 20 ++++++++++++++++---- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 86f3eae0a..db3241e35 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -433,7 +433,7 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty [type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu); } - if (wxGetApp().get_mode() == comExpert) { + if (wxGetApp().get_mode() >= comAdvanced) { sub_menu->AppendSeparator(); append_menu_item(sub_menu, wxID_ANY, _L("Gallery"), "", [type](wxCommandEvent&) { obj_list()->load_subobject(type, true); }, "", menu); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a2d64d72c..b7027404e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2517,7 +2517,7 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D } -void ObjectList::update_info_items(size_t obj_idx) +void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/) { const ModelObject* model_object = (*m_objects)[obj_idx]; wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx); @@ -2565,9 +2565,21 @@ void ObjectList::update_info_items(size_t obj_idx) } else if (shows && ! should_show) { - Unselect(item); + if (!selections) + Unselect(item); m_objects_model->Delete(item); - Select(item_obj); + if (selections) { + if (selections->Index(item) != wxNOT_FOUND) { + // If info item was deleted from the list, + // it's need to be deleted from selection array, if it was there + selections->Remove(item); + // Select item_obj, if info_item doesn't exist for item anymore, but was selected + if (selections->Index(item_obj) == wxNOT_FOUND) + selections->Add(item_obj); + } + } + else + Select(item_obj); } } } @@ -3760,7 +3772,7 @@ void ObjectList::update_object_list_by_printer_technology() for (auto& object_item : object_items) { // update custom supports info - update_info_items(m_objects_model->GetObjectIdByItem(object_item)); + update_info_items(m_objects_model->GetObjectIdByItem(object_item), &sel); // Update Settings Item for object update_settings_item_and_selection(object_item, sel); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index cc34e348a..a57947044 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -350,7 +350,7 @@ public: void update_and_show_object_settings_item(); void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); void update_object_list_by_printer_technology(); - void update_info_items(size_t obj_idx); + void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); void instances_to_separated_objects(const int obj_idx); From e76b54b7708d03bcaece1fa9303d52229aff60cc Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 4 Aug 2021 15:43:22 +0200 Subject: [PATCH 20/26] Follow-up of bc81c22e (renamed the new CLI option --dont-ensure-on-bed to avoid double negatives) --- src/PrusaSlicer.cpp | 9 +++------ src/libslic3r/PrintConfig.cpp | 7 ++++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 21b161456..3490b8183 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -258,7 +258,6 @@ int CLI::run(int argc, char **argv) Points bed = get_bed_shape(m_print_config); ArrangeParams arrange_cfg; arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config)); - bool user_ensure_on_bed = true; for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { @@ -331,10 +330,8 @@ int CLI::run(int argc, char **argv) } } else if (opt_key == "dont_arrange") { // do nothing - this option alters other transform options - } else if (opt_key == "dont_ensure_on_bed") { - // Remember that we saw this so we don't lift objects from the bed - // after the other transformations are processed. - user_ensure_on_bed = false; + } else if (opt_key == "ensure_on_bed") { + // do nothing, the value is used later } else if (opt_key == "rotate") { for (auto &model : m_models) for (auto &o : model.objects) @@ -439,7 +436,7 @@ int CLI::run(int argc, char **argv) // All transforms have been dealt with. Now ensure that the objects are on bed. // (Unless the user said otherwise.) - if (user_ensure_on_bed) + if (m_config.opt_bool("ensure_on_bed")) for (auto &model : m_models) for (auto &o : model.objects) o->ensure_on_bed(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 16c16f038..ed7961ce1 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4154,9 +4154,10 @@ CLITransformConfigDef::CLITransformConfigDef() def->label = L("Don't arrange"); def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates."); - def = this->add("dont_ensure_on_bed", coBool); - def->label = L("Don't ensure on bed"); - def->tooltip = L("Do not lift the object above the bed when it is partially below."); + def = this->add("ensure_on_bed", coBool); + def->label = L("Ensure on bed"); + def->tooltip = L("Lift the object above the bed when it is partially below. Enabled by default, use --no-ensure-on-bed to disable."); + def->set_default_value(new ConfigOptionBool(true)); def = this->add("duplicate", coInt); def->label = L("Duplicate"); From 215ee293ae705943e28ab1509f50d03c85dd26a0 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 5 Aug 2021 13:02:54 +0200 Subject: [PATCH 21/26] CLI parsing: allow giving explicit values for bool options, improved error reporting: It is now possible to use e.g. --ensure-on-bed=0 for bools (meaning the same as --no-ensure-on-bed). Using --no- prefix on non-boolean is an error (--no-ensure-on-bed=1) Providing a value for --no- prefixed bool is an error (--no-loglevel 5) --- src/libslic3r/Config.cpp | 45 +++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index b9f9b266d..cbc434b39 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -871,6 +871,7 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option token.erase(equals_pos); } } + // Look for the cli -> option mapping. const auto it = opts.find(token); if (it == opts.end()) { @@ -879,15 +880,46 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option } const t_config_option_key opt_key = it->second; const ConfigOptionDef &optdef = this->def()->options.at(opt_key); + // If the option type expects a value and it was not already provided, // look for it in the next token. - if (optdef.type != coBool && optdef.type != coBools && value.empty()) { - if (i == (argc-1)) { - boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; + if (value.empty()) { + if (optdef.type != coBool && optdef.type != coBools) { + if (i == (argc-1)) { + boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; + return false; + } + value = argv[++ i]; + } else { + // This is a bool or bools. The value is optional, but may still be there. + // Check if the next token can be deserialized into ConfigOptionBool. + // If it is in fact bools, it will be rejected later anyway. + if (i != argc-1) { // There is still a token to read. + ConfigOptionBool cobool; + if (cobool.deserialize(argv[i+1])) + value = argv[++i]; + } + } + + } + + if (no) { + if (optdef.type != coBool && optdef.type != coBools) { + boost::nowide::cerr << "Only boolean config options can be negated with --no- prefix." << std::endl; + return false; + } + else if (! value.empty()) { + boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl; return false; } - value = argv[++ i]; } + if (optdef.type == coBools && ! value.empty()) { + boost::nowide::cerr << "Vector boolean options cannot have a value. Fill them in by " + "repeating them and negate by --no- prefix." << std::endl; + return false; + } + + // Store the option value. const bool existing = this->has(opt_key); if (keys != nullptr && ! existing) { @@ -911,7 +943,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option // unescaped by the calling shell. opt_vector->deserialize(value, true); } else if (opt_base->type() == coBool) { - static_cast(opt_base)->value = !no; + if (value.empty()) + static_cast(opt_base)->value = !no; + else + opt_base->deserialize(value); } else if (opt_base->type() == coString) { // Do not unescape single string values, the unescaping is left to the calling shell. static_cast(opt_base)->value = value; From 413dc8d6ec1630573ab159cef899599fd755816f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 5 Aug 2021 15:18:18 +0200 Subject: [PATCH 22/26] Revert "Restoring custom supports/seams after reload from disk" This reverts commit d001195ebd2239ba21f31a908d1c146e7af03c08. It makes no sense, reload from disk is used when the file has changed, which means the paint-on data are possibly meaningless or even completely wrong (referencing triangles that no longer exist) --- src/slic3r/GUI/Plater.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 723c93da2..665ae8088 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3423,9 +3423,6 @@ void Plater::priv::reload_from_disk() new_volume->convert_from_imperial_units(); if (old_volume->source.is_converted_from_meters) new_volume->convert_from_meters(); - new_volume->supported_facets.assign(old_volume->supported_facets); - new_volume->seam_facets.assign(old_volume->seam_facets); - new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets); std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); if (!sinking) From 49fdf3da7b32bf0dfaa8f199fc20c76148825f42 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 5 Aug 2021 15:35:44 +0200 Subject: [PATCH 23/26] follow-up to a86e7107a5d4549cf313c4097f87c309c3ff4d2c: Make is_converted_from_meters / is_converted_from_inches exclusive-or. Maybe it would be better to make a single enum from the two booleans, if they are exclusive-or? --- src/libslic3r/Format/3mf.cpp | 3 ++- src/libslic3r/Format/AMF.cpp | 3 ++- src/libslic3r/Model.cpp | 27 +++++++++++++++++++-------- src/slic3r/GUI/Plater.cpp | 10 ++++++---- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 613b7444a..16d86ac28 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2860,9 +2860,10 @@ namespace Slic3r { stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; } + assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); if (volume->source.is_converted_from_inches) stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - if (volume->source.is_converted_from_meters) + else if (volume->source.is_converted_from_meters) stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n"; } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index d03cfd4fa..0312c7f22 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -1241,9 +1241,10 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " " << volume->source.mesh_offset(1) << "\n"; stream << " " << volume->source.mesh_offset(2) << "\n"; } + assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); if (volume->source.is_converted_from_inches) stream << " 1\n"; - if (volume->source.is_converted_from_meters) + else if (volume->source.is_converted_from_meters) stream << " 1\n"; stream << std::setprecision(std::numeric_limits::max_digits10); const indexed_triangle_set &its = volume->mesh().its; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ba743f49c..c45acc048 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -460,13 +460,15 @@ void Model::convert_multipart_object(unsigned int max_extruders) this->objects.push_back(object); } +static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3; + bool Model::looks_like_imperial_units() const { if (this->objects.size() == 0) return false; for (ModelObject* obj : this->objects) - if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3; + if (obj->get_object_stl_stats().volume < volume_threshold_inches) return true; return false; @@ -474,22 +476,26 @@ bool Model::looks_like_imperial_units() const void Model::convert_from_imperial_units(bool only_small_volumes) { - double in_to_mm = 25.4; + static constexpr const in_to_mm = 25.4; for (ModelObject* obj : this->objects) - if (! only_small_volumes || obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3; + if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) { obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); - for (ModelVolume* v : obj->volumes) + for (ModelVolume* v : obj->volumes) { + assert(! v->source.is_converted_from_meters); v->source.is_converted_from_inches = true; + } } } +static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1 + bool Model::looks_like_saved_in_meters() const { if (this->objects.size() == 0) return false; for (ModelObject* obj : this->objects) - if (obj->get_object_stl_stats().volume < 0.001) // 0.001 = 0.1*0.1*0.1; + if (obj->get_object_stl_stats().volume < volume_threshold_meters) return true; return false; @@ -497,12 +503,14 @@ bool Model::looks_like_saved_in_meters() const void Model::convert_from_meters(bool only_small_volumes) { - double m_to_mm = 1000; + static constexpr const double m_to_mm = 1000; for (ModelObject* obj : this->objects) - if (! only_small_volumes || obj->get_object_stl_stats().volume < 0.001) { // 0.001 = 0.1*0.1*0.1; + if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_meters) { obj->scale_mesh_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm)); - for (ModelVolume* v : obj->volumes) + for (ModelVolume* v : obj->volumes) { + assert(! v->source.is_converted_from_inches); v->source.is_converted_from_meters = true; + } } } @@ -1075,6 +1083,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH; if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER) vol->source.is_converted_from_meters = conv_type == ConversionType::CONV_FROM_METER; + assert(! vol->source.is_converted_from_inches || ! vol->source.is_converted_from_meters); } else vol->set_offset(volume->get_offset()); @@ -1827,6 +1836,7 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand void ModelVolume::convert_from_imperial_units() { + assert(! this->source.is_converted_from_meters); double in_to_mm = 25.4; this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); this->set_offset(Vec3d(0, 0, 0)); @@ -1835,6 +1845,7 @@ void ModelVolume::convert_from_imperial_units() void ModelVolume::convert_from_meters() { + assert(! this->source.is_converted_from_inches); double m_to_mm = 1000; this->scale_geometry_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm)); this->set_offset(Vec3d(0, 0, 0)); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 665ae8088..4da29afdf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2388,8 +2388,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ //wxMessageDialog msg_dlg(q, _L( MessageDialog msg_dlg(q, _L( "This file contains several objects positioned at multiple heights.\n" - "Instead of considering them as multiple objects, should I consider\n" - "this file as a single object having multiple parts?") + "\n", + "Instead of considering them as multiple objects, should \n" + "should the file be loaded as a single object having multiple parts?") + "\n", _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO); if (msg_dlg.ShowModal() == wxID_YES) { model.convert_multipart_object(nozzle_dmrs->values.size()); @@ -3211,9 +3211,10 @@ void Plater::priv::replace_with_stl() new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation()); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) new_volume->convert_from_imperial_units(); - if (old_volume->source.is_converted_from_meters) + else if (old_volume->source.is_converted_from_meters) new_volume->convert_from_meters(); new_volume->supported_facets.assign(old_volume->supported_facets); new_volume->seam_facets.assign(old_volume->seam_facets); @@ -3419,9 +3420,10 @@ void Plater::priv::reload_from_disk() new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation()); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) new_volume->convert_from_imperial_units(); - if (old_volume->source.is_converted_from_meters) + else if (old_volume->source.is_converted_from_meters) new_volume->convert_from_meters(); std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); From 54897aeac0ca70afa9c0e002795d29eb9af8e9bd Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 5 Aug 2021 17:33:41 +0200 Subject: [PATCH 24/26] Fixed previous commit --- src/libslic3r/Model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index c45acc048..538358177 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -476,7 +476,7 @@ bool Model::looks_like_imperial_units() const void Model::convert_from_imperial_units(bool only_small_volumes) { - static constexpr const in_to_mm = 25.4; + static constexpr const double in_to_mm = 25.4; for (ModelObject* obj : this->objects) if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) { obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); From 01f32e18d6e5fc520a947d3b1b59cb41bf6d9c20 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 6 Aug 2021 13:03:30 +0200 Subject: [PATCH 25/26] Fixed build on Linux, abs->std::abs --- src/libslic3r/QuadricEdgeCollapse.cpp | 7 ++++--- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index e42ed5deb..a66dbc192 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -2,6 +2,7 @@ #include #include "MutablePriorityQueue.hpp" #include "SimplifyMeshImpl.hpp" +#include using namespace Slic3r; @@ -316,7 +317,7 @@ double QuadricEdgeCollapse::calculate_error(uint32_t id_v1, const Vertices &vertices) { double det = calculate_determinant(q); - if (abs(det) < std::numeric_limits::epsilon()) { + if (std::abs(det) < std::numeric_limits::epsilon()) { // can't divide by zero auto verts = create_vertices(id_v1, id_v2, vertices); auto errors = vertices_error(q, verts); @@ -333,7 +334,7 @@ Vec3f QuadricEdgeCollapse::calculate_vertex(uint32_t id_v1, const Vertices &vertices) { double det = calculate_determinant(q); - if (abs(det) < std::numeric_limits::epsilon()) { + if (std::abs(det) < std::numeric_limits::epsilon()) { // can't divide by zero auto verts = create_vertices(id_v1, id_v2, vertices); auto errors = vertices_error(q, verts); @@ -650,4 +651,4 @@ void QuadricEdgeCollapse::compact(const VertexInfos & v_infos, its.indices[ti_new++] = its.indices[ti]; } its.indices.erase(its.indices.begin() + ti_new, its.indices.end()); -} \ No newline at end of file +} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 92654c606..694eeadd4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -9,6 +9,9 @@ #include "libslic3r/Model.hpp" #include "libslic3r/QuadricEdgeCollapse.hpp" +#include +#include + namespace Slic3r::GUI { GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, @@ -274,8 +277,8 @@ void GLGizmoSimplify::process() state = State::settings; } // need to render last status fn - // without Sleep it freeze until mouse move - Sleep(50); + // without sleep it freezes until mouse move + std::this_thread::sleep_for(std::chrono::milliseconds(50)); m_parent.schedule_extra_frame(0); }); } From 85c7dea1a98530a09c4485fbbf582dd6012f6cf3 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 6 Aug 2021 16:02:32 +0200 Subject: [PATCH 26/26] Finished concept of gizmos with no toolbar icon: on_is_selectable and on_is_activable functions are now completely independent, the former says if there shall be an icon in the left toolbar, the latter says if the gizmo can be activated (by a shortcut or GLGizmoManager::open_gizmo) --- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 5 ++++ .../GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 27 ++++++++----------- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 - 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index f6e7708fa..5bf2c1556 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -51,6 +51,11 @@ bool GLGizmoMmuSegmentation::on_is_selectable() const && wxGetApp().get_mode() != comSimple && wxGetApp().extruders_edited_cnt() > 1); } +bool GLGizmoMmuSegmentation::on_is_activable() const +{ + return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1; +} + static std::vector> get_extruders_colors() { unsigned char rgb_color[3] = {}; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index e890ca031..b1b19bfac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -109,6 +109,7 @@ protected: std::string on_get_name() const override; bool on_is_selectable() const override; + bool on_is_activable() const override; wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 317d7ebca..507aeb021 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -515,7 +515,7 @@ bool GLGizmoPainterBase::on_is_activable() const const Selection& selection = m_parent.get_selection(); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF - || !selection.is_single_full_instance()) + || !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple) return false; // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 49c3e8dbd..bd3637360 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -40,7 +40,7 @@ protected: virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual bool on_is_activable() const override; - virtual bool on_is_selectable() const override { return false; }; + virtual bool on_is_selectable() const override { return false; } private: void close(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 8871fec23..eda95a2a5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -51,14 +51,7 @@ std::vector GLGizmosManager::get_selectable_idxs() const return out; } -std::vector GLGizmosManager::get_activable_idxs() const -{ - std::vector out; - for (size_t i=0; iis_activable()) - out.push_back(i); - return out; -} + size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const { @@ -171,7 +164,7 @@ void GLGizmosManager::refresh_on_off_state() return; if (m_current != Undefined - && (! m_gizmos[m_current]->is_activable() || ! m_gizmos[m_current]->is_selectable())) { + && ! m_gizmos[m_current]->is_activable()) { activate_gizmo(Undefined); update_data(); } @@ -189,7 +182,7 @@ void GLGizmosManager::reset_all_states() bool GLGizmosManager::open_gizmo(EType type) { int idx = int(type); - if (/*m_gizmos[idx]->is_selectable() &&*/ m_gizmos[idx]->is_activable()) { + if (m_gizmos[idx]->is_activable()) { activate_gizmo(m_current == idx ? Undefined : (EType)idx); update_data(); return true; @@ -306,7 +299,7 @@ bool GLGizmosManager::handle_shortcut(int key) auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(), [key](const std::unique_ptr& gizmo) { int gizmo_key = gizmo->get_shortcut_key(); - return gizmo->is_selectable() + return gizmo->is_activable() && ((gizmo_key == key - 64) || (gizmo_key == key - 96)); }); @@ -1079,8 +1072,7 @@ void GLGizmosManager::do_render_overlay() const float u_offset = 1.0f / (float)tex_width; float v_offset = 1.0f / (float)tex_height; - float toolbar_top = 0.f; - float current_y = 0.f; + float current_y = FLT_MAX; for (size_t idx : selectable_idxs) { GLGizmoBase* gizmo = m_gizmos[idx].get(); @@ -1094,15 +1086,18 @@ void GLGizmosManager::do_render_overlay() const float u_right = u_left + du - u_offset; GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); - if (idx == m_current) { - toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height(); + if (idx == m_current || current_y == FLT_MAX) { + // The FLT_MAX trick is here so that even non-selectable but activable + // gizmos are passed some meaningful value. current_y = 0.5f * cnv_h - zoomed_top_y * zoom; } zoomed_top_y -= zoomed_stride_y; } - if (m_current != Undefined) + if (m_current != Undefined) { + float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height(); m_gizmos[m_current]->render_input_window(width, current_y, toolbar_top); + } } float GLGizmosManager::get_scaled_total_height() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index db628a0b7..cdfb3f6ff 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -102,7 +102,6 @@ private: std::pair m_highlight; // bool true = higlightedShown, false = highlightedHidden std::vector get_selectable_idxs() const; - std::vector get_activable_idxs() const; size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const; void activate_gizmo(EType type);