From 9750701f43c60361a1ee274a4e8e501488483b29 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 0fd1930d574..090accb6a40 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 2a64bd9cf48a4fe996ca9fed16e6c787f246b47f 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 1e85a10b645..d7fb937d04a 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 425e11f73eb..115a675aca9 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 293da2ca9a8c5c7a0a69d8b19d1253283b0453a3 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 572b37c7f9c..2f2ca5fb52e 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 ae02c42b38d..4aa72c95f1d 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 735728e89a9..1b4d74c02d8 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 5ee14c52611..788fe90c0c5 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 02beb8d5745b37baeabdc02b1c5ae7953b1947d0 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 81897553c31..2ae726b9719 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 085db8705d9..3c2a0810bae 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 4196dfdac42..b08a2ef800a 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 fc62d4c3420..be6c71f6ccf 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 f306dff98e6..ad1834733da 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 acdf891173b9c8d2454991641dce1a4ab2d4d7ba 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 40111c16fc9831e0ddb85dd5657462990d0c7b46 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 ad1834733da..c4d46341be2 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 5f9478447d924147399fb832ad43ffdcd3ed6316 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 65d5524c2db..be0e20eb202 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 9d6a19169b9..3fd6b9f04fd 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 ef8336d39fd..aebe52631c7 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 9b07157fd30..3f2dc5a4479 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 e9742c94d932e281e485720151fcf965db0834f1 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 f7593ba1624..f189378a6da 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 09c18103b78cbcd527dc5d06d65128e5012997d1 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 ef90402a9aa..aa2763968d5 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 4b7614a88de441560c0793f19df37801a170887c 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 c4d46341be2..5429e2658e5 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 fa0744e4e9e46b4135c507e7b68410cf378bd2a3 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 ad823c55ded..758b3ab4d5d 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 442b999a6d3fa1c735deb1672206743646313d0a 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 65284ffaca2..8903ee0a2af 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 9223fe9291668e29bfec5fc444e60781c73044ed 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 8903ee0a2af..95475b0d7cb 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 a66c6fbdfaa7135b4138ecc9a51e7938bdabe9a1 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 0b9614fa219..e6006ef5844 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 fda500810bf..11dcfa77574 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 ba631c3ee52..1854800cc7f 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 7aa39c82390..8ebe1eb1015 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 71cd429e70cf3d79c5f41fc4b851f9c690daf287 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 57722042342..0e252ac6f4b 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 82fd04bce1f..818519be491 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 7022426c43f618f86e7ed63ef61cfea162407513 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 b08a2ef800a..9333efb2057 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 be8012e87bade5977fb1f7f2cc2bb44dfe0b3fb5 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 be44d32a487..723c93da21f 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 47bfe5ab3ed6ca9f2a5f8adc4398e37d983c48a0 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 b3e6841c9db..21b161456fc 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 e6006ef5844..ba743f49caa 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 51284311026..16c16f038e2 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 a422a815a49c6c78c5dc2856b0d7483a3daed04a 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 86f3eae0afd..db3241e35ef 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 a2d64d72cfd..b7027404eba 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 cc34e348a6a..a57947044eb 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 3371e2f48416cc1eb8aac3aee628720c92010860 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 21b161456fc..3490b81836c 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 16c16f038e2..ed7961ce108 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 2e4189b5136063e4a3632d233b31eb40302f0508 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 b9f9b266dc4..cbc434b3950 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 93923c3dec857243981956a1fb465bc95378528e 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 723c93da21f..665ae808804 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 bc5cc8e463a5c8741defed53a71570943b0e1055 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 613b7444af1..16d86ac284d 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 d03cfd4fa02..0312c7f2251 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 ba743f49caa..c45acc04844 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 665ae808804..4da29afdf41 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 fe9c07f9baee11e826e94b01650ea6b00ede6c01 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 c45acc04844..53835817780 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 a4ea221f5b00839490ee0d0d3b6d3cb71e8e100b 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 e42ed5deb25..a66dbc192a5 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 92654c60640..694eeadd4e7 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 ef2ce7f7d8486c3e54b1eba5429abbe75b90da70 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 f6e7708faea..5bf2c1556af 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 e890ca0312f..b1b19bfaca0 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 317d7ebcaa4..507aeb021b2 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 49c3e8dbda4..bd363736071 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 8871fec23ed..eda95a2a55b 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 db628a0b767..cdfb3f6ff7f 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);