From 589d2be442d93e019c7fc8ccf26fa15d6f61be5d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 8 Mar 2021 09:36:13 +0100 Subject: [PATCH 01/60] Fix of Repair with Netfabb does not work on builds after 2.3.0 release (Windows 10) #6193 This is more a workaround than a fix: Windows 10 3D model fixing API refuses to load a zip64 encoded 3MF. We need to get in touch with Microsoft on that issue, for now the 3MFs generated for the Windows 10 3D model fixing API will be limited to 4GB. Saving a bigger 3MF will fail. --- src/libslic3r/Format/3mf.cpp | 19 +++++++++++++------ src/libslic3r/Format/3mf.hpp | 2 +- src/slic3r/Utils/FixModelByWin10.cpp | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index e10b26f38..8ba248998 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2014,9 +2014,10 @@ namespace Slic3r { typedef std::map IdToObjectDataMap; bool m_fullpath_sources{ true }; + bool m_zip64 { true }; public: - bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr); + bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64); private: bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); @@ -2036,10 +2037,11 @@ namespace Slic3r { bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); }; - bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) { clear_errors(); m_fullpath_sources = fullpath_sources; + m_zip64 = zip64; return _save_model_to_file(filename, model, config, thumbnail_data); } @@ -2233,9 +2235,13 @@ namespace Slic3r { { mz_zip_writer_staged_context context; if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(), - // Maximum expected and allowed 3MF file size is 16GiB. - // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. - (uint64_t(1) << 30) * 16, + m_zip64 ? + // Maximum expected and allowed 3MF file size is 16GiB. + // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. + (uint64_t(1) << 30) * 16 : + // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see + // GH issue #6193. + (uint64_t(1) << 32) - 1, nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) { add_error("Unable to add model file to archive"); return false; @@ -2926,12 +2932,13 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c return res; } -bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) +bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) { if (path == nullptr || model == nullptr) return false; _3MF_Exporter exporter; + exporter.zip64 = zip64; bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data); if (!res) exporter.log_errors(); diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index ccfd9356d..a09a1b834 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -33,7 +33,7 @@ namespace Slic3r { // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices - extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr); + extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr, bool zip64 = true); } // namespace Slic3r diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index bcab6daaf..7933a1d69 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -363,7 +363,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) ModelObject *model_object = model.add_object(); model_object->add_volume(*volumes[ivolume]); model_object->add_instance(); - if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false)) { + if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) { boost::filesystem::remove(path_src); throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed")); } From ed7be17bf1142bffbe0f99e95a01270225235626 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 8 Mar 2021 09:38:14 +0100 Subject: [PATCH 02/60] Detection of Win10 3D printing API from the default Visual Studio env variables. --- CMakeLists.txt | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 98ae4f0c6..a3b944532 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,21 +146,33 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # We pick it from environment if it is not defined in another way if(WIN32) - if(NOT DEFINED WIN10SDK_PATH) - if(DEFINED ENV{WIN10SDK_PATH}) - set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") - endif() + if(NOT DEFINED WIN10SDK_PATH) + if(DEFINED ENV{WIN10SDK_PATH}) + set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") endif() - if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") - message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") - message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") - message("STL fixing by the Netfabb service will not be compiled") - unset(WIN10SDK_PATH) + endif() + if(DEFINED WIN10SDK_PATH) + if (EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") + set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}/Include") + else() + message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") + message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") + message("STL fixing by the Netfabb service will not be compiled") + unset(WIN10SDK_PATH) endif() - if(WIN10SDK_PATH) + else() + # Try to use the default Windows 10 SDK path. + set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}") + if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h") + message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found") + message("STL fixing by the Netfabb service will not be compiled") + unset(WIN10SDK_INCLUDE_PATH) + endif() + endif() + if(WIN10SDK_INCLUDE_PATH) message("Building with Win10 Netfabb STL fixing service support") add_definitions(-DHAS_WIN10SDK) - include_directories("${WIN10SDK_PATH}/Include") + include_directories("${WIN10SDK_INCLUDE_PATH}") else() message("Building without Win10 Netfabb STL fixing service support") endif() From 8adb495e7dad4759d5a3a37089af1095cf9056e3 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 8 Mar 2021 11:29:52 +0100 Subject: [PATCH 03/60] Fix of 589d2be442d93e019c7fc8ccf26fa15d6f61be5d --- src/libslic3r/Format/3mf.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 8ba248998..152d72079 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2938,8 +2938,7 @@ bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, return false; _3MF_Exporter exporter; - exporter.zip64 = zip64; - bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data); + bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64); if (!res) exporter.log_errors(); From 7cb3e729eee70d708396e0f17f657c49ac45757f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 8 Mar 2021 14:19:58 +0100 Subject: [PATCH 04/60] Fixed #6182 - First line custom gcode not aligned left in the tool tip --- src/slic3r/GUI/DoubleSlider.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index c12d36112..81945061b 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1317,7 +1317,12 @@ wxString Control::get_tooltip(int tick/*=-1*/) "This code won't be processed during G-code generation."); // Show custom Gcode as a first string of tooltop - tooltip = " "; + std::string space = " "; + tooltip = space; + auto format_gcode = [space](std::string gcode) { + boost::replace_all(gcode, "\n", "\n" + space); + return gcode; + }; tooltip += tick_code_it->type == ColorChange ? (m_mode == SingleExtruder ? @@ -1329,7 +1334,7 @@ wxString Control::get_tooltip(int tick/*=-1*/) format_wxstr(_L("Custom template (\"%1%\")"), gcode(Template)) : tick_code_it->type == ToolChange ? format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : - from_u8(tick_code_it->extra);// tick_code_it->type == Custom + from_u8(format_gcode(tick_code_it->extra));// tick_code_it->type == Custom // If tick is marked as a conflict (exclamation icon), // we should to explain why From e57eca0289e2f5e3e6bb2f6d6fa53e0fc48987c3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Dec 2020 13:38:09 +0100 Subject: [PATCH 05/60] Add voxel scale to openvdb metadata. To be able to retrieve that information from a generated grid alone. To avoid the copying of input mesh (for scaling) when doing the hollowing Also remove some unused stuff from OpenVDBUtils --- src/libslic3r/OpenVDBUtils.cpp | 110 +++++++++++++------------------- src/libslic3r/OpenVDBUtils.hpp | 14 ++-- src/libslic3r/SLA/Hollowing.cpp | 36 +++++------ 3 files changed, 70 insertions(+), 90 deletions(-) diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 0f5bfa157..259cd1cbd 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -22,74 +22,52 @@ namespace Slic3r { class TriangleMeshDataAdapter { public: const TriangleMesh &mesh; - + float voxel_scale; + size_t polygonCount() const { return mesh.its.indices.size(); } size_t pointCount() const { return mesh.its.vertices.size(); } size_t vertexCount(size_t) const { return 3; } - + // Return position pos in local grid index space for polygon n and vertex v - void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; + // The actual mesh will appear to openvdb as scaled uniformly by voxel_size + // And the voxel count per unit volume can be affected this way. + void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const + { + auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v))); + Slic3r::Vec3d p = mesh.its.vertices[vidx].cast() * voxel_scale; + pos = {p.x(), p.y(), p.z()}; + } + + TriangleMeshDataAdapter(const TriangleMesh &m, float voxel_sc = 1.f) + : mesh{m}, voxel_scale{voxel_sc} {}; }; -class Contour3DDataAdapter { -public: - const sla::Contour3D &mesh; - - size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); } - size_t pointCount() const { return mesh.points.size(); } - size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; } - - // Return position pos in local grid index space for polygon n and vertex v - void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; -}; - -void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n, - size_t v, - openvdb::Vec3d &pos) const -{ - auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v))); - Slic3r::Vec3d p = mesh.its.vertices[vidx].cast(); - pos = {p.x(), p.y(), p.z()}; -} - -void Contour3DDataAdapter::getIndexSpacePoint(size_t n, - size_t v, - openvdb::Vec3d &pos) const -{ - size_t vidx = 0; - if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v))); - else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v))); - - Slic3r::Vec3d p = mesh.points[vidx]; - pos = {p.x(), p.y(), p.z()}; -} - - // TODO: Do I need to call initialize? Seems to work without it as well but the // docs say it should be called ones. It does a mutex lock-unlock sequence all // even if was called previously. -openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, +openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh, const openvdb::math::Transform &tr, - float exteriorBandWidth, - float interiorBandWidth, - int flags) + float voxel_scale, + float exteriorBandWidth, + float interiorBandWidth, + int flags) { openvdb::initialize(); TriangleMeshPtrs meshparts = mesh.split(); auto it = std::remove_if(meshparts.begin(), meshparts.end(), - [](TriangleMesh *m){ - m->require_shared_vertices(); - return !m->is_manifold() || m->volume() < EPSILON; - }); + [](TriangleMesh *m){ + m->require_shared_vertices(); + return !m->is_manifold() || m->volume() < EPSILON; + }); meshparts.erase(it, meshparts.end()); openvdb::FloatGrid::Ptr grid; for (TriangleMesh *m : meshparts) { auto subgrid = openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, + TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, interiorBandWidth, flags); if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); @@ -106,19 +84,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, interiorBandWidth, flags); } - return grid; -} + grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); -openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh, - const openvdb::math::Transform &tr, - float exteriorBandWidth, - float interiorBandWidth, - int flags) -{ - openvdb::initialize(); - return openvdb::tools::meshToVolume( - Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth, - flags); + return grid; } template @@ -128,20 +96,25 @@ sla::Contour3D _volumeToMesh(const Grid &grid, bool relaxDisorientedTriangles) { openvdb::initialize(); - + std::vector points; std::vector triangles; std::vector quads; - + openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue, adaptivity, relaxDisorientedTriangles); - + + float scale = 1.; + try { + scale = grid.template metaValue("voxel_scale"); + } catch (...) { } + sla::Contour3D ret; ret.points.reserve(points.size()); ret.faces3.reserve(triangles.size()); ret.faces4.reserve(quads.size()); - for (auto &v : points) ret.points.emplace_back(to_vec3d(v)); + for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale); for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v)); for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v)); @@ -166,9 +139,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid, relaxDisorientedTriangles); } -openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir) +openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, + double iso, + double er, + double ir) { - return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir)); + auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso), + float(er), float(ir)); + + // Copies voxel_scale metadata, if it exists. + new_grid->insertMeta(*grid.deepCopyMeta()); + + return new_grid; } } // namespace Slic3r diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index aa4b5154a..5df816abb 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -21,14 +21,16 @@ inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; } inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; } +// Here voxel_scale defines the scaling of voxels which affects the voxel count. +// 1.0 value means a voxel for every unit cube. 2 means the model is scaled to +// be 2x larger and the voxel count is increased by the increment in the scaled +// volume, thus 4 times. This kind a sampling accuracy selection is not +// achievable through the Transform parameter. (TODO: or is it?) +// The resulting grid will contain the voxel_scale in its metadata under the +// "voxel_scale" key to be used in grid_to_mesh function. openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh, const openvdb::math::Transform &tr = {}, - float exteriorBandWidth = 3.0f, - float interiorBandWidth = 3.0f, - int flags = 0); - -openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D & mesh, - const openvdb::math::Transform &tr = {}, + float voxel_scale = 1.f, float exteriorBandWidth = 3.0f, float interiorBandWidth = 3.0f, int flags = 0); diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 6df752fd3..61e1e122b 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -32,48 +32,44 @@ static TriangleMesh _generate_interior(const TriangleMesh &mesh, double voxel_scale, double closing_dist) { - TriangleMesh imesh{mesh}; - - _scale(voxel_scale, imesh); - double offset = voxel_scale * min_thickness; double D = voxel_scale * closing_dist; float out_range = 0.1f * float(offset); float in_range = 1.1f * float(offset + D); - + if (ctl.stopcondition()) return {}; else ctl.statuscb(0, L("Hollowing")); - - auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range); - + + auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range); + assert(gridptr); - + if (!gridptr) { BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL"; return {}; } - + if (ctl.stopcondition()) return {}; else ctl.statuscb(30, L("Hollowing")); - + + double iso_surface = D; + auto narrowb = double(in_range); if (closing_dist > .0) { - gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range)); + gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); } else { - D = -offset; + iso_surface = -offset; } - + if (ctl.stopcondition()) return {}; else ctl.statuscb(70, L("Hollowing")); - - double iso_surface = D; + double adaptivity = 0.; + auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); - - _scale(1. / voxel_scale, omesh); - + if (ctl.stopcondition()) return {}; else ctl.statuscb(100, L("Hollowing")); - + return omesh; } From 82954ba7152dc05f76445614e17ed97edbe0091e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Dec 2020 16:38:04 +0100 Subject: [PATCH 06/60] Group hollowing result (including grid) into one struct --- src/libslic3r/SLA/Hollowing.cpp | 104 +++++++++++++++++++++-------- src/libslic3r/SLA/Hollowing.hpp | 23 +++++-- src/libslic3r/SLAPrint.cpp | 5 +- src/libslic3r/SLAPrint.hpp | 4 +- src/libslic3r/SLAPrintSteps.cpp | 28 +++++--- tests/libslic3r/test_hollowing.cpp | 30 ++++----- tests/sla_print/sla_test_utils.cpp | 6 +- 7 files changed, 133 insertions(+), 67 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 61e1e122b..44358cebe 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -26,11 +26,36 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); } template> inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } -static TriangleMesh _generate_interior(const TriangleMesh &mesh, - const JobController &ctl, - double min_thickness, - double voxel_scale, - double closing_dist) +struct Interior { + TriangleMesh mesh; + openvdb::FloatGrid::Ptr gridptr; + double closing_distance = 0.; + double thickness = 0.; + double voxel_scale = 1.; + double nb_in = 3.; + double nb_out = 3.; +}; + +void InteriorDeleter::operator()(Interior *p) +{ + delete p; +} + +TriangleMesh &get_mesh(Interior &interior) +{ + return interior.mesh; +} + +const TriangleMesh &get_mesh(const Interior &interior) +{ + return interior.mesh; +} + +static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, + const JobController &ctl, + double min_thickness, + double voxel_scale, + double closing_dist) { double offset = voxel_scale * min_thickness; double D = voxel_scale * closing_dist; @@ -64,22 +89,30 @@ static TriangleMesh _generate_interior(const TriangleMesh &mesh, else ctl.statuscb(70, L("Hollowing")); double adaptivity = 0.; + InteriorPtr interior = InteriorPtr{new Interior{}}; - auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); + interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); + interior->gridptr = gridptr; if (ctl.stopcondition()) return {}; else ctl.statuscb(100, L("Hollowing")); - return omesh; + interior->closing_distance = D; + interior->thickness = offset; + interior->voxel_scale = voxel_scale; + interior->nb_in = narrowb; + interior->nb_out = narrowb; + + return interior; } -std::unique_ptr generate_interior(const TriangleMesh & mesh, - const HollowingConfig &hc, - const JobController & ctl) +InteriorPtr generate_interior(const TriangleMesh & mesh, + const HollowingConfig &hc, + const JobController & ctl) { static const double MIN_OVERSAMPL = 3.; static const double MAX_OVERSAMPL = 8.; - + // I can't figure out how to increase the grid resolution through openvdb // API so the model will be scaled up before conversion and the result // scaled down. Voxels have a unit size. If I set voxelSize smaller, it @@ -88,26 +121,29 @@ std::unique_ptr generate_interior(const TriangleMesh & mesh, // // max 8x upscale, min is native voxel size auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality; - auto meshptr = std::make_unique( - _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, - hc.closing_distance)); - - if (meshptr && !meshptr->empty()) { - + + InteriorPtr interior = + generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale, + hc.closing_distance); + + if (interior && !interior->mesh.empty()) { + // This flips the normals to be outward facing... - meshptr->require_shared_vertices(); - indexed_triangle_set its = std::move(meshptr->its); - + interior->mesh.require_shared_vertices(); + indexed_triangle_set its = std::move(interior->mesh.its); + Slic3r::simplify_mesh(its); - + // flip normals back... for (stl_triangle_vertex_indices &ind : its.indices) std::swap(ind(0), ind(2)); - - *meshptr = Slic3r::TriangleMesh{its}; + + interior->mesh = Slic3r::TriangleMesh{its}; + interior->mesh.repaired = true; + interior->mesh.require_shared_vertices(); } - - return meshptr; + + return interior; } Contour3D DrainHole::to_mesh() const @@ -269,12 +305,22 @@ void cut_drainholes(std::vector & obj_slices, obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]); } -void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg) +void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags) { - std::unique_ptr inter_ptr = - Slic3r::sla::generate_interior(mesh); + InteriorPtr interior = generate_interior(mesh, cfg, JobController{}); + if (!interior) return; - if (inter_ptr) mesh.merge(*inter_ptr); + hollow_mesh(mesh, *interior, flags); +} + +void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) +{ + if (mesh.empty() || interior.mesh.empty()) return; + +// if (flags & hfRemoveInsideTriangles && interior.gridptr) +// erase_inside_triangles_2(mesh, interior); + + mesh.merge(interior.mesh); mesh.require_shared_vertices(); } diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 949cc2393..356907846 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -19,6 +19,17 @@ struct HollowingConfig bool enabled = true; }; +enum HollowingFlags { hfRemoveInsideTriangles = 0x1 }; + +// All data related to a generated mesh interior. Includes the 3D grid and mesh +// and various metadata. No need to manipulate from outside. +struct Interior; +struct InteriorDeleter { void operator()(Interior *p); }; +using InteriorPtr = std::unique_ptr; + +TriangleMesh & get_mesh(Interior &interior); +const TriangleMesh &get_mesh(const Interior &interior); + struct DrainHole { Vec3f pos; @@ -60,11 +71,15 @@ using DrainHoles = std::vector; constexpr float HoleStickOutLength = 1.f; -std::unique_ptr generate_interior(const TriangleMesh &mesh, - const HollowingConfig & = {}, - const JobController &ctl = {}); +InteriorPtr generate_interior(const TriangleMesh &mesh, + const HollowingConfig & = {}, + const JobController &ctl = {}); -void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg); +// Will do the hollowing +void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); + +// Hollowing prepared in "interior", merge with original mesh +void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 16b068cb9..07042692a 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1149,8 +1149,9 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const { - if (m_hollowing_data && m_config.hollowing_enable.getBool()) - return m_hollowing_data->interior; + if (m_hollowing_data && m_hollowing_data->interior && + m_config.hollowing_enable.getBool()) + return sla::get_mesh(*m_hollowing_data->interior); return EMPTY_MESH; } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index bed66ab4f..87ab3db8a 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -327,8 +327,8 @@ private: class HollowingData { public: - - TriangleMesh interior; + + sla::InteriorPtr interior; mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index d8bea62ae..976e6d3d1 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -131,13 +131,14 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) double quality = po.m_config.hollowing_quality.getFloat(); double closing_d = po.m_config.hollowing_closing_distance.getFloat(); sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; - auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); - if (meshptr->empty()) + sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg); + + if (!interior || sla::get_mesh(*interior).empty()) BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; else { po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); - po.m_hollowing_data->interior = *meshptr; + po.m_hollowing_data->interior = std::move(interior); } } @@ -145,7 +146,9 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) void SLAPrint::Steps::drill_holes(SLAPrintObject &po) { bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty(); - bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()); + bool is_hollowed = + (po.m_hollowing_data && po.m_hollowing_data->interior && + !sla::get_mesh(*po.m_hollowing_data->interior).empty()); if (! is_hollowed && ! needs_drilling) { // In this case we can dump any data that might have been @@ -163,10 +166,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // holes that are no longer on the frontend. TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; hollowed_mesh = po.transformed_mesh(); - if (! po.m_hollowing_data->interior.empty()) { - hollowed_mesh.merge(po.m_hollowing_data->interior); - hollowed_mesh.require_shared_vertices(); - } + sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior/*, sla::hfRemoveInsideTriangles*/); if (! needs_drilling) { BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; @@ -260,9 +260,15 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) auto &slice_grid = po.m_model_height_levels; slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); - if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) { - po.m_hollowing_data->interior.repair(true); - TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior); + sla::Interior *interior = po.m_hollowing_data ? + po.m_hollowing_data->interior.get() : + nullptr; + + if (interior && ! sla::get_mesh(*interior).empty()) { + TriangleMesh interiormesh = sla::get_mesh(*interior); + interiormesh.repaired = false; + interiormesh.repair(true); + TriangleMeshSlicer interior_slicer(&interiormesh); std::vector interior_slices; interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index 65b87c2a2..2218a27b7 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -26,21 +26,19 @@ static Slic3r::TriangleMesh load_model(const std::string &obj_filename) } -TEST_CASE("Negative 3D offset should produce smaller object.", "[Hollowing]") -{ - Slic3r::TriangleMesh in_mesh = load_model("20mm_cube.obj"); - Benchmark bench; - bench.start(); - - std::unique_ptr out_mesh_ptr = - Slic3r::sla::generate_interior(in_mesh); - - bench.stop(); - - std::cout << "Elapsed processing time: " << bench.getElapsedSec() << std::endl; - - if (out_mesh_ptr) in_mesh.merge(*out_mesh_ptr); - in_mesh.require_shared_vertices(); - in_mesh.WriteOBJFile("merged_out.obj"); +TEST_CASE("Hollow two overlapping spheres") { + using namespace Slic3r; + + TriangleMesh sphere1 = make_sphere(10., 2 * PI / 20.), sphere2 = sphere1; + + sphere1.translate(-5.f, 0.f, 0.f); + sphere2.translate( 5.f, 0.f, 0.f); + + sphere1.merge(sphere2); + sphere1.require_shared_vertices(); + + sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles); + + sphere1.WriteOBJFile("twospheres.obj"); } diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 653221cd3..1ec890beb 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -88,9 +88,9 @@ void test_supports(const std::string &obj_filename, REQUIRE_FALSE(mesh.empty()); if (hollowingcfg.enabled) { - auto inside = sla::generate_interior(mesh, hollowingcfg); - REQUIRE(inside); - mesh.merge(*inside); + sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg); + REQUIRE(interior); + mesh.merge(sla::get_mesh(*interior)); mesh.require_shared_vertices(); } From 527e6752941bf8336fba91e63de65cf8fb4da544 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 4 Jan 2021 14:12:40 +0100 Subject: [PATCH 07/60] Use triangle removal only for visualized mesh --- src/libslic3r/SLA/Hollowing.cpp | 5 +++++ src/libslic3r/SLA/Hollowing.hpp | 2 ++ src/libslic3r/SLAPrint.hpp | 5 +++++ src/libslic3r/SLAPrintSteps.cpp | 9 ++++++--- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 44358cebe..a350e6faa 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -324,4 +324,9 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) mesh.require_shared_vertices(); } +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) +{ + +} + }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 356907846..caa7d7b6b 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -81,6 +81,8 @@ void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); // Hollowing prepared in "interior", merge with original mesh void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior); + void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, float closing_radius, diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 87ab3db8a..74c71dc1e 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -85,6 +85,10 @@ public: // Get the mesh that is going to be printed with all the modifications // like hollowing and drilled holes. const TriangleMesh & get_mesh_to_print() const { + return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh(); + } + + const TriangleMesh & get_mesh_to_slice() const { return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh(); } @@ -330,6 +334,7 @@ private: sla::InteriorPtr interior; mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh + mutable TriangleMesh hollow_mesh_with_holes_trimmed; }; std::unique_ptr m_hollowing_data; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 976e6d3d1..59ad20c72 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -166,7 +166,10 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // holes that are no longer on the frontend. TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; hollowed_mesh = po.transformed_mesh(); - sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior/*, sla::hfRemoveInsideTriangles*/); + sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); + + TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; + sla::remove_inside_triangles(mesh_view, *po.m_hollowing_data->interior); if (! needs_drilling) { BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; @@ -213,7 +216,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // same imaginary grid (the height vector argument to TriangleMeshSlicer). void SLAPrint::Steps::slice_model(SLAPrintObject &po) { - const TriangleMesh &mesh = po.get_mesh_to_print(); + const TriangleMesh &mesh = po.get_mesh_to_slice(); // We need to prepare the slice index... @@ -303,7 +306,7 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // If supports are disabled, we can skip the model scan. if(!po.m_config.supports_enable.getBool()) return; - const TriangleMesh &mesh = po.get_mesh_to_print(); + const TriangleMesh &mesh = po.get_mesh_to_slice(); if (!po.m_supportdata) po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); From d48ca7fd030119a926518c1c5705854471d4a5ed Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 17:03:33 +0100 Subject: [PATCH 08/60] Fix incorrect mesh shown on plater after hollowing --- src/libslic3r/SLAPrint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 07042692a..42ed8b80f 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1120,7 +1120,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const return this->pad_mesh(); case slaposDrillHoles: if (m_hollowing_data) - return m_hollowing_data->hollow_mesh_with_holes; + return get_mesh_to_print(); [[fallthrough]]; default: return TriangleMesh(); From dd202af8cd3a96977acfefc26c898989129f6485 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 26 Feb 2021 15:37:26 +0100 Subject: [PATCH 09/60] Fix stl export with hollowed mesh --- 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 7a38dd3a1..3d4d381fb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5363,7 +5363,7 @@ void Plater::export_stl(bool extended, bool selection_only) inst_mesh.merge(inst_supports_mesh); } - TriangleMesh inst_object_mesh = object->get_mesh_to_print(); + TriangleMesh inst_object_mesh = object->get_mesh_to_slice(); inst_object_mesh.transform(mesh_trafo_inv); inst_object_mesh.transform(inst_transform, is_left_handed); From 06bf02df695ee70f266a5c33c55f73f5f7714d1d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 26 Feb 2021 15:37:48 +0100 Subject: [PATCH 10/60] Fix Gizmo preview with hollowed mesh --- src/libslic3r/SLAPrintSteps.cpp | 305 ++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 18 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 2 + src/slic3r/GUI/MeshUtils.cpp | 19 ++ src/slic3r/GUI/MeshUtils.hpp | 3 + 5 files changed, 194 insertions(+), 153 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 59ad20c72..4637aa761 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -84,17 +84,17 @@ SLAPrint::Steps::Steps(SLAPrint *print) void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o) { if (o == soSupport && !po.m_supportdata) return; - + auto faded_lyrs = size_t(po.m_config.faded_layers.getInt()); double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.; double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat(); - + double doffs = m_print->m_printer_config.absolute_correction.getFloat(); coord_t clpr_offs = scaled(doffs); - + faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs); size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1); - + auto efc = [start_efc, faded_lyrs_efc](size_t pos) { return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc; }; @@ -102,13 +102,13 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin std::vector &slices = o == soModel ? po.m_model_slices : po.m_supportdata->support_slices; - + if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) { size_t idx = po.m_slice_index[i].get_slice_idx(o); if (idx < slices.size()) slices[idx] = offset_ex(slices[idx], float(clpr_offs)); } - + if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) { size_t idx = po.m_slice_index[i].get_slice_idx(o); if (idx < slices.size()) @@ -124,7 +124,7 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; return; } - + BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; double thickness = po.m_config.hollowing_min_thickness.getFloat(); @@ -169,16 +169,17 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; - sla::remove_inside_triangles(mesh_view, *po.m_hollowing_data->interior); + mesh_view = po.transformed_mesh(); + sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); if (! needs_drilling) { BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; return; } - + BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; sla::DrainHoles drainholes = po.transformed_drainhole_points(); - + std::uniform_real_distribution dist(0., float(EPSILON)); auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}); for (sla::DrainHole holept : drainholes) { @@ -190,12 +191,12 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m); MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m); } - + if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) throw Slic3r::SlicingError(L("Too many overlapping holes.")); - + auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); - + try { MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); @@ -215,11 +216,11 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // of it. In any case, the model and the supports have to be sliced in the // same imaginary grid (the height vector argument to TriangleMeshSlicer). void SLAPrint::Steps::slice_model(SLAPrintObject &po) -{ +{ const TriangleMesh &mesh = po.get_mesh_to_slice(); // We need to prepare the slice index... - + double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat(); float lh = float(lhd); coord_t lhs = scaled(lhd); @@ -229,40 +230,40 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) auto minZf = float(minZ); coord_t minZs = scaled(minZ); coord_t maxZs = scaled(maxZ); - + po.m_slice_index.clear(); - + size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); po.m_slice_index.reserve(cap); - + po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); - + for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) po.m_slice_index.emplace_back(h, unscaled(h) - lh / 2.f, lh); - + // Just get the first record that is from the model: auto slindex_it = po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); - + if(slindex_it == po.m_slice_index.end()) //TRN To be shown at the status bar on SLA slicing error. throw Slic3r::RuntimeError( L("Slicing had to be stopped due to an internal error: " "Inconsistent slice index.")); - + po.m_model_height_levels.clear(); po.m_model_height_levels.reserve(po.m_slice_index.size()); for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) po.m_model_height_levels.emplace_back(it->slice_level()); - + TriangleMeshSlicer slicer(&mesh); - + po.m_model_slices.clear(); float closing_r = float(po.config().slice_closing_radius.value); auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); - + sla::Interior *interior = po.m_hollowing_data ? po.m_hollowing_data->interior.get() : nullptr; @@ -282,17 +283,17 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) diff_ex(po.m_model_slices[i], slice); }); } - + auto mit = slindex_it; for (size_t id = 0; id < po.m_model_slices.size() && mit != po.m_slice_index.end(); id++) { mit->set_model_slice_idx(po, id); ++mit; } - + // We apply the printer correction offset here. apply_printer_corrections(po, soModel); - + if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool()) { po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); @@ -305,22 +306,22 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) { // If supports are disabled, we can skip the model scan. if(!po.m_config.supports_enable.getBool()) return; - + const TriangleMesh &mesh = po.get_mesh_to_slice(); - + if (!po.m_supportdata) po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); - + const ModelObject& mo = *po.m_model_object; - + BOOST_LOG_TRIVIAL(debug) << "Support point count " << mo.sla_support_points.size(); - + // Unless the user modified the points or we already did the calculation, // we will do the autoplacement. Otherwise we will just blindly copy the // frontend data into the backend cache. if (mo.sla_points_status != sla::PointsStatus::UserModified) { - + // calculate heights of slices (slices are calculated already) const std::vector& heights = po.m_model_height_levels; @@ -328,27 +329,27 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // calculated on slices, the algorithm then raycasts the points // so they actually lie on the mesh. // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); - + throw_if_canceled(); sla::SupportPointGenerator::Config config; const SLAPrintObjectConfig& cfg = po.config(); - + // the density config value is in percents: config.density_relative = float(cfg.support_points_density_relative / 100.f); config.minimal_distance = float(cfg.support_points_minimal_distance); config.head_diameter = float(cfg.support_head_front_diameter); - + // scaling for the sub operations double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; double init = current_status(); - + auto statuscb = [this, d, init](unsigned st) { double current = init + st * d; if(std::round(current_status()) < std::round(current)) report_status(current, OBJ_STEP_LABELS(slaposSupportPoints)); }; - + // Construction of this object does the calculation. throw_if_canceled(); sla::SupportPointGenerator auto_supports( @@ -359,10 +360,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) const std::vector& points = auto_supports.output(); throw_if_canceled(); po.m_supportdata->pts = points; - + BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " << po.m_supportdata->pts.size(); - + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass // the update status to GLGizmoSlaSupports report_status(-1, L("Generating support points"), @@ -377,9 +378,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) void SLAPrint::Steps::support_tree(SLAPrintObject &po) { if(!po.m_supportdata) return; - + sla::PadConfig pcfg = make_pad_cfg(po.m_config); - + if (pcfg.embed_object) po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); @@ -389,15 +390,15 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) remove_bottom_points(po.m_supportdata->pts, float(po.m_supportdata->emesh.ground_level() + EPSILON)); } - + po.m_supportdata->cfg = make_support_cfg(po.m_config); // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); - + // scaling for the sub operations double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; double init = current_status(); sla::JobController ctl; - + ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { double current = init + st * d; if (std::round(current_status()) < std::round(current)) @@ -406,26 +407,26 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) }; ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - + po.m_supportdata->create_support_tree(ctl); - + if (!po.m_config.supports_enable.getBool()) return; - + throw_if_canceled(); - + // Create the unified mesh auto rc = SlicingStatus::RELOAD_SCENE; - + // This is to prevent "Done." being displayed during merged_mesh() report_status(-1, L("Visualizing supports")); - + BOOST_LOG_TRIVIAL(debug) << "Processed support point count " << po.m_supportdata->pts.size(); - + // Check the mesh for later troubleshooting. if(po.support_mesh().empty()) BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; - + report_status(-1, L("Visualizing supports"), rc); } @@ -433,15 +434,15 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { // this step can only go after the support tree has been created // and before the supports had been sliced. (or the slicing has to be // repeated) - + if(po.m_config.pad_enable.getBool()) { // Get the distilled pad configuration from the config sla::PadConfig pcfg = make_pad_cfg(po.m_config); - + ExPolygons bp; // This will store the base plate of the pad. double pad_h = pcfg.full_height(); const TriangleMesh &trmesh = po.transformed_mesh(); - + if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { // No support (thus no elevation) or zero elevation mode // we sometimes call it "builtin pad" is enabled so we will @@ -451,19 +452,19 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { float(po.m_config.layer_height.getFloat()), [this](){ throw_if_canceled(); }); } - + po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); - + if (!validate_pad(pad_mesh, pcfg)) throw Slic3r::SlicingError( L("No pad can be generated for this model with the " "current configuration")); - + } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { po.m_supportdata->support_tree_ptr->remove_pad(); } - + throw_if_canceled(); report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE); } @@ -473,25 +474,25 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { // be part of the slices) void SLAPrint::Steps::slice_supports(SLAPrintObject &po) { auto& sd = po.m_supportdata; - + if(sd) sd->support_slices.clear(); - + // Don't bother if no supports and no pad is present. if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool()) return; - + if(sd && sd->support_tree_ptr) { auto heights = reserve_vector(po.m_slice_index.size()); - + for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level()); sd->support_slices = sd->support_tree_ptr->slice( heights, float(po.config().slice_closing_radius.value)); } - - for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) + + for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) po.m_slice_index[i].set_support_slice_idx(po, i); - + apply_printer_corrections(po, soSupport); // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update @@ -506,37 +507,37 @@ using ClipperPolygons = std::vector; static ClipperPolygons polyunion(const ClipperPolygons &subjects) { ClipperLib::Clipper clipper; - + bool closed = true; - + for(auto& path : subjects) { clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); } - + auto mode = ClipperLib::pftPositive; - + return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); } static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips) { ClipperLib::Clipper clipper; - + bool closed = true; - + for(auto& path : subjects) { clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); } - + for(auto& path : clips) { clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); } - + auto mode = ClipperLib::pftPositive; - + return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); } @@ -544,28 +545,28 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) { namespace sl = libnest2d::sl; - + if (!record.print_obj()) return {}; - + ClipperPolygons polygons; auto &input_polygons = record.get_slice(o); auto &instances = record.print_obj()->instances(); bool is_lefthanded = record.print_obj()->is_left_handed(); polygons.reserve(input_polygons.size() * instances.size()); - + for (const ExPolygon& polygon : input_polygons) { if(polygon.contour.empty()) continue; - + for (size_t i = 0; i < instances.size(); ++i) { ClipperPolygon poly; - + // We need to reverse if is_lefthanded is true but bool needreverse = is_lefthanded; - + // should be a move poly.Contour.reserve(polygon.contour.size() + 1); - + auto& cntr = polygon.contour.points; if(needreverse) for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) @@ -573,12 +574,12 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o else for(auto& p : cntr) poly.Contour.emplace_back(p.x(), p.y()); - + for(auto& h : polygon.holes) { poly.Holes.emplace_back(); auto& hole = poly.Holes.back(); hole.reserve(h.points.size() + 1); - + if(needreverse) for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) hole.emplace_back(it->x(), it->y()); @@ -586,42 +587,42 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o for(auto& p : h.points) hole.emplace_back(p.x(), p.y()); } - + if(is_lefthanded) { for(auto& p : poly.Contour) p.X = -p.X; for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X; } - + sl::rotate(poly, double(instances[i].rotation)); sl::translate(poly, ClipperPoint{instances[i].shift.x(), instances[i].shift.y()}); - + polygons.emplace_back(std::move(poly)); } } - + return polygons; } void SLAPrint::Steps::initialize_printer_input() { auto &printer_input = m_print->m_printer_input; - + // clear the rasterizer input printer_input.clear(); - + size_t mx = 0; for(SLAPrintObject * o : m_print->m_objects) { if(auto m = o->get_slice_index().size() > mx) mx = m; } - + printer_input.reserve(mx); - + auto eps = coord_t(SCALED_EPSILON); - + for(SLAPrintObject * o : m_print->m_objects) { coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; - + for(const SliceRecord& slicerecord : o->get_slice_index()) { if (!slicerecord.is_valid()) throw Slic3r::SlicingError( @@ -630,7 +631,7 @@ void SLAPrint::Steps::initialize_printer_input() "objects printable.")); coord_t lvlid = slicerecord.print_level() - gndlvl; - + // Neat trick to round the layer levels to the grid. lvlid = eps * (lvlid / eps); @@ -640,8 +641,8 @@ void SLAPrint::Steps::initialize_printer_input() if(it == printer_input.end() || it->level() != lvlid) it = printer_input.insert(it, PrintLayer(lvlid)); - - + + it->add(slicerecord); } } @@ -650,53 +651,53 @@ void SLAPrint::Steps::initialize_printer_input() // Merging the slices from all the print objects into one slice grid and // calculating print statistics from the merge result. void SLAPrint::Steps::merge_slices_and_eval_stats() { - + initialize_printer_input(); - + auto &print_statistics = m_print->m_print_statistics; auto &printer_config = m_print->m_printer_config; auto &material_config = m_print->m_material_config; auto &printer_input = m_print->m_printer_input; - + print_statistics.clear(); - + // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); }; - + const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%); const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0; const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0; - + const double init_exp_time = material_config.initial_exposure_time.getFloat(); const double exp_time = material_config.exposure_time.getFloat(); - + const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20] - + const auto width = scaled(printer_config.display_width.getFloat()); const auto height = scaled(printer_config.display_height.getFloat()); const double display_area = width*height; - + double supports_volume(0.0); double models_volume(0.0); - + double estim_time(0.0); std::vector layers_times; layers_times.reserve(printer_input.size()); - + size_t slow_layers = 0; size_t fast_layers = 0; - + const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); double fade_layer_time = init_exp_time; - + sla::ccr::SpinningMutex mutex; using Lock = std::lock_guard; - + // Going to parallel: auto printlayerfn = [this, // functions and read only vars areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, - + // write vars &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, &fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt) @@ -705,87 +706,87 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // vector of slice record references auto& slicerecord_references = layer.slices(); - + if(slicerecord_references.empty()) return; - + // Layer height should match for all object slices for a given level. const auto l_height = double(slicerecord_references.front().get().layer_height()); - + // Calculation of the consumed material - + ClipperPolygons model_polygons; ClipperPolygons supports_polygons; - + size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), size_t(0), [](size_t a, const SliceRecord &sr) { return a + sr.get_slice(soModel).size(); }); - + model_polygons.reserve(c); - + c = std::accumulate(layer.slices().begin(), layer.slices().end(), size_t(0), [](size_t a, const SliceRecord &sr) { return a + sr.get_slice(soModel).size(); }); - + supports_polygons.reserve(c); - + for(const SliceRecord& record : layer.slices()) { - + ClipperPolygons modelslices = get_all_polygons(record, soModel); for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); - + ClipperPolygons supportslices = get_all_polygons(record, soSupport); for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); - + } - + model_polygons = polyunion(model_polygons); double layer_model_area = 0; for (const ClipperPolygon& polygon : model_polygons) layer_model_area += areafn(polygon); - + if (layer_model_area < 0 || layer_model_area > 0) { Lock lck(mutex); models_volume += layer_model_area * l_height; } - + if(!supports_polygons.empty()) { if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons); else supports_polygons = polydiff(supports_polygons, model_polygons); // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType } - + double layer_support_area = 0; for (const ClipperPolygon& polygon : supports_polygons) layer_support_area += areafn(polygon); - + if (layer_support_area < 0 || layer_support_area > 0) { Lock lck(mutex); supports_volume += layer_support_area * l_height; } - + // Here we can save the expensively calculated polygons for printing ClipperPolygons trslices; trslices.reserve(model_polygons.size() + supports_polygons.size()); for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); - + layer.transformed_slices(polyunion(trslices)); - + // Calculation of the slow and fast layers to the future controlling those values on FW - + const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt; - + { Lock lck(mutex); if (is_fast_layer) fast_layers++; else slow_layers++; - + // Calculation of the printing time double layer_times = 0.0; @@ -803,15 +804,15 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { estim_time += layer_times; } }; - + // sequential version for debugging: // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn); - + auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; print_statistics.support_used_material = supports_volume * SCALING2; print_statistics.objects_used_material = models_volume * SCALING2; - + // Estimated printing time // A layers count o the highest object if (printer_input.size() == 0) @@ -820,10 +821,10 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { print_statistics.estimated_print_time = estim_time; print_statistics.layers_times = layers_times; } - + print_statistics.fast_layers_count = fast_layers; print_statistics.slow_layers_count = slow_layers; - + report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); } @@ -831,23 +832,23 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { void SLAPrint::Steps::rasterize() { if(canceled() || !m_print->m_printer) return; - + // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state double sd = (100 - max_objstatus) / 100.0; - + // slot is the portion of 100% that is realted to rasterization unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; - + // pst: previous state double pst = current_status(); - + double increment = (slot * sd) / m_print->m_printer_input.size(); double dstatus = current_status(); - + sla::ccr::SpinningMutex slck; using Lock = std::lock_guard; - + // procedure to process one height level. This will run in parallel auto lvlfn = [this, &slck, increment, &dstatus, &pst] @@ -855,10 +856,10 @@ void SLAPrint::Steps::rasterize() { PrintLayer& printlayer = m_print->m_printer_input[idx]; if(canceled()) return; - + for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) raster.draw(poly); - + // Status indication guarded with the spinlock { Lock lck(slck); @@ -870,10 +871,10 @@ void SLAPrint::Steps::rasterize() } } }; - + // last minute escape if(canceled()) return; - + // Print all the layers in parallel m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index a34c7562e..7f6b10670 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -200,12 +200,20 @@ void HollowedMesh::on_update() if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; if (timestamp > m_old_hollowing_timestamp) { - const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); + const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); if (! backend_mesh.empty()) { m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); m_hollowed_mesh_transformed->transform(trafo_inv); m_old_hollowing_timestamp = timestamp; + + const TriangleMesh &interior = print_object->hollowed_interior_mesh(); + if (!interior.empty()) { + m_hollowed_interior_transformed = std::make_unique(interior); + m_hollowed_interior_transformed->repaired = false; + m_hollowed_interior_transformed->repair(true); + m_hollowed_interior_transformed->transform(trafo_inv); + } } else m_hollowed_mesh_transformed.reset(nullptr); @@ -230,6 +238,10 @@ const TriangleMesh* HollowedMesh::get_hollowed_mesh() const return m_hollowed_mesh_transformed.get(); } +const TriangleMesh* HollowedMesh::get_hollowed_interior() const +{ + return m_hollowed_interior_transformed.get(); +} @@ -306,6 +318,10 @@ void ObjectClipper::on_update() m_clippers.back()->set_mesh(*mesh); } m_old_meshes = meshes; + + if (has_hollowed) + m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); + m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); //if (has_hollowed && m_clp_ratio != 0.) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 61c273297..ace256748 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -199,6 +199,7 @@ public: #endif // NDEBUG const TriangleMesh* get_hollowed_mesh() const; + const TriangleMesh* get_hollowed_interior() const; protected: void on_update() override; @@ -206,6 +207,7 @@ protected: private: std::unique_ptr m_hollowed_mesh_transformed; + std::unique_ptr m_hollowed_interior_transformed; size_t m_old_hollowing_timestamp = 0; int m_print_object_idx = -1; int m_print_objects_count = 0; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index ee0abe76f..f9ccfd0d6 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -2,6 +2,7 @@ #include "libslic3r/Tesselate.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/ClipperUtils.hpp" #include "slic3r/GUI/Camera.hpp" @@ -31,6 +32,15 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) } } +void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) +{ + if (m_negative_mesh != &mesh) { + m_negative_mesh = &mesh; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + void MeshClipper::set_transformation(const Geometry::Transformation& trafo) @@ -74,6 +84,15 @@ void MeshClipper::recalculate_triangles() std::vector list_of_expolys; m_tms->set_up_direction(up.cast()); m_tms->slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); + + if (m_negative_mesh && !m_negative_mesh->empty()) { + TriangleMeshSlicer negative_tms{m_negative_mesh}; + negative_tms.set_up_direction(up.cast()); + + std::vector neg_polys; + negative_tms.slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &neg_polys, [](){}); + list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); + } m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); // Rotate the cut into world coords: diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 60dcb30c8..09caf199b 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -78,6 +78,8 @@ public: // must make sure that it stays valid. void set_mesh(const TriangleMesh& mesh); + void set_negative_mesh(const TriangleMesh &mesh); + // Inform the MeshClipper about the transformation that transforms the mesh // into world coordinates. void set_transformation(const Geometry::Transformation& trafo); @@ -91,6 +93,7 @@ private: Geometry::Transformation m_trafo; const TriangleMesh* m_mesh = nullptr; + const TriangleMesh* m_negative_mesh = nullptr; ClippingPlane m_plane; std::vector m_triangles2d; GLIndexedVertexArray m_vertex_array; From 195b39bb5bc47b07828ca2c1d0de8dd2107c1c4b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 24 Feb 2021 18:56:01 +0100 Subject: [PATCH 11/60] Eliminate memory leaks from hollowing code --- src/libslic3r/OpenVDBUtils.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 259cd1cbd..e09fceed7 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -54,18 +54,20 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh, { openvdb::initialize(); - TriangleMeshPtrs meshparts = mesh.split(); + TriangleMeshPtrs meshparts_raw = mesh.split(); + auto meshparts = reserve_vector>(meshparts_raw.size()); + for (auto *p : meshparts_raw) + meshparts.emplace_back(p); - auto it = std::remove_if(meshparts.begin(), meshparts.end(), - [](TriangleMesh *m){ - m->require_shared_vertices(); - return !m->is_manifold() || m->volume() < EPSILON; - }); + auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) { + m->require_shared_vertices(); + return m->volume() < EPSILON; + }); meshparts.erase(it, meshparts.end()); openvdb::FloatGrid::Ptr grid; - for (TriangleMesh *m : meshparts) { + for (auto &m : meshparts) { auto subgrid = openvdb::tools::meshToVolume( TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, interiorBandWidth, flags); From 7830c8f8aa2d8a447b0848771728575ba42c8618 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 4 Jan 2021 15:41:58 +0100 Subject: [PATCH 12/60] Add BoundingBox constructor with point set iterators --- src/libslic3r/BoundingBox.hpp | 52 ++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 8de28af5c..37483fc3e 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -21,22 +21,30 @@ public: min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : min(p1), max(p1), defined(false) { merge(p2); merge(p3); } - BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero()) + + template > + BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) { - if (points.empty()) { + if (from == to) { this->defined = false; // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor"); } else { - typename std::vector::const_iterator it = points.begin(); - this->min = *it; - this->max = *it; - for (++ it; it != points.end(); ++ it) { - this->min = this->min.cwiseMin(*it); - this->max = this->max.cwiseMax(*it); + auto it = from; + this->min = it->template cast(); + this->max = this->min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + this->min = this->min.cwiseMin(vec); + this->max = this->max.cwiseMax(vec); } this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); } } + + BoundingBoxBase(const std::vector &points) + : BoundingBoxBase(points.begin(), points.end()) + {} + void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); } void merge(const PointClass &point); void merge(const std::vector &points); @@ -74,19 +82,27 @@ public: { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : BoundingBoxBase(p1, p1) { merge(p2); merge(p3); } - BoundingBox3Base(const std::vector& points) + + template > BoundingBox3Base(It from, It to) { - if (points.empty()) + if (from == to) throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); - typename std::vector::const_iterator it = points.begin(); - this->min = *it; - this->max = *it; - for (++ it; it != points.end(); ++ it) { - this->min = this->min.cwiseMin(*it); - this->max = this->max.cwiseMax(*it); + + auto it = from; + this->min = it->template cast(); + this->max = this->min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + this->min = this->min.cwiseMin(vec); + this->max = this->max.cwiseMax(vec); } this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2)); } + + BoundingBox3Base(const std::vector &points) + : BoundingBox3Base(points.begin(), points.end()) + {} + void merge(const PointClass &point); void merge(const std::vector &points); void merge(const BoundingBox3Base &bb); @@ -188,9 +204,7 @@ public: class BoundingBoxf3 : public BoundingBox3Base { public: - BoundingBoxf3() : BoundingBox3Base() {} - BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base(pmin, pmax) {} - BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {} + using BoundingBox3Base::BoundingBox3Base; BoundingBoxf3 transformed(const Transform3d& matrix) const; }; From e3c2e513fa599a31480979b535e4cfe9346d5806 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 25 Feb 2021 15:49:50 +0100 Subject: [PATCH 13/60] Do grid redistance even with zero closing distance This prevents having a leftover grid with zero at the exterior boundary. Trimming expects zero at (offset + closing distance) inwards --- src/libslic3r/SLA/Hollowing.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index a350e6faa..ccc0caf98 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -79,11 +79,7 @@ static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, double iso_surface = D; auto narrowb = double(in_range); - if (closing_dist > .0) { - gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); - } else { - iso_surface = -offset; - } + gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); if (ctl.stopcondition()) return {}; else ctl.statuscb(70, L("Hollowing")); From b8c1c136667c8d4ab679a571478fa653220a0b1f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 18:20:11 +0100 Subject: [PATCH 14/60] Add max_concurrency method for various execution policies --- src/libslic3r/SLA/Concurrency.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index b692914ac..8ff0ff809 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -76,13 +77,18 @@ template<> struct _ccr from, to, init, std::forward(mergefn), [](typename I::value_type &i) { return i; }, granularity); } + + static size_t max_concurreny() + { + return tbb::this_task_arena::max_concurrency(); + } }; template<> struct _ccr { private: struct _Mtx { inline void lock() {} inline void unlock() {} }; - + public: using SpinningMutex = _Mtx; using BlockingMutex = _Mtx; @@ -133,6 +139,8 @@ public: return reduce(from, to, init, std::forward(mergefn), [](typename I::value_type &i) { return i; }); } + + static size_t max_concurreny() { return 1; } }; using ccr = _ccr; From 1ec154012ec8e1017a71f883356314894acd0b66 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 18:24:57 +0100 Subject: [PATCH 15/60] Add working version of triangle trimming for hollowed meshes --- src/libslic3r/SLA/Hollowing.cpp | 245 +++++++++++++++++++++++++++++++- 1 file changed, 241 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index ccc0caf98..632917266 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -29,11 +29,21 @@ inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } struct Interior { TriangleMesh mesh; openvdb::FloatGrid::Ptr gridptr; + mutable std::optional accessor; + double closing_distance = 0.; double thickness = 0.; double voxel_scale = 1.; - double nb_in = 3.; - double nb_out = 3.; + double nb_in = 3.; // narrow band width inwards + double nb_out = 3.; // narrow band width outwards + // Full narrow band is the sum of the two above values. + + void reset_accessor() const // This resets the accessor and its cache + // Not a thread safe call! + { + if (gridptr) + accessor = gridptr->getConstAccessor(); + } }; void InteriorDeleter::operator()(Interior *p) @@ -313,16 +323,243 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) { if (mesh.empty() || interior.mesh.empty()) return; -// if (flags & hfRemoveInsideTriangles && interior.gridptr) -// erase_inside_triangles_2(mesh, interior); + if (flags & hfRemoveInsideTriangles && interior.gridptr) + remove_inside_triangles(mesh, interior); mesh.merge(interior.mesh); mesh.require_shared_vertices(); } +// Get the distance of p to the interior's zero iso_surface. Interior should +// have its zero isosurface positioned at offset + closing_distance inwards form +// the model surface. +static double get_distance_raw(const Vec3f &p, const Interior &interior) +{ + assert(interior.gridptr); + + if (!interior.accessor) interior.reset_accessor(); + + auto v = (p * interior.voxel_scale).cast(); + auto grididx = interior.gridptr->transform().worldToIndexCellCentered( + {v.x(), v.y(), v.z()}); + + return interior.accessor->getValue(grididx) ; +} + +struct TriangleBubble { Vec3f center; double R; }; + +// Return the distance of bubble center to the interior boundary or NaN if the +// triangle is too big to be measured. +static double get_distance(const TriangleBubble &b, const Interior &interior) +{ + double R = b.R * interior.voxel_scale; + double D = get_distance_raw(b.center, interior); + + return (D > 0. && R >= interior.nb_out) || + (D < 0. && R >= interior.nb_in) || + ((D - R) < 0. && 2 * R > interior.thickness) ? + std::nan("") : + // FIXME: Adding interior.voxel_scale is a compromise supposed + // to prevent the deletion of the triangles forming the interior + // itself. This has a side effect that a small portion of the + // bad triangles will still be visible. + D - interior.closing_distance /*+ 2 * interior.voxel_scale*/; +} + +double get_distance(const Vec3f &p, const Interior &interior) +{ + double d = get_distance_raw(p, interior) - interior.closing_distance; + return d / interior.voxel_scale; +} + +// A face that can be divided. Stores the indices into the original mesh if its +// part of that mesh and the vertices it consists of. +enum { NEW_FACE = -1}; +struct DivFace { + Vec3i indx; + std::array verts; + long faceid = NEW_FACE; + long parent = NEW_FACE; +}; + +// Divide a face recursively and call visitor on all the sub-faces. +template +void divide_triangle(const DivFace &face, Fn &&visitor) +{ + std::array edges = {(face.verts[0] - face.verts[1]), + (face.verts[1] - face.verts[2]), + (face.verts[2] - face.verts[0])}; + + std::array edgeidx = {0, 1, 2}; + + std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) { + return edges[e1].squaredNorm() > edges[e2].squaredNorm(); + }); + + DivFace child1, child2; + + child1.parent = face.faceid == NEW_FACE ? face.parent : face.faceid; + child1.indx(0) = -1; + child1.indx(1) = face.indx(edgeidx[1]); + child1.indx(2) = face.indx((edgeidx[1] + 1) % 3); + child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.; + child1.verts[1] = face.verts[edgeidx[1]]; + child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3]; + + if (visitor(child1)) + divide_triangle(child1, std::forward(visitor)); + + child2.parent = face.faceid == NEW_FACE ? face.parent : face.faceid; + child2.indx(0) = -1; + child2.indx(1) = face.indx(edgeidx[2]); + child2.indx(2) = face.indx((edgeidx[2] + 1) % 3); + child2.verts[0] = child1.verts[0]; + child2.verts[1] = face.verts[edgeidx[2]]; + child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3]; + + if (visitor(child2)) + divide_triangle(child2, std::forward(visitor)); +} + void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) { + enum TrPos { posInside, posTouch, posOutside }; + auto &faces = mesh.its.indices; + auto &vertices = mesh.its.vertices; + auto bb = mesh.bounding_box(); + + // TODO: Parallel mode not working yet + using exec_policy = ccr_seq; + + // Info about the needed modifications on the input mesh. + struct MeshMods { + + // Just a thread safe wrapper for a vector of triangles. + struct { + std::vector> data; + exec_policy::SpinningMutex mutex; + + void emplace_back(const std::array &pts) + { + std::lock_guard lk{mutex}; + data.emplace_back(pts); + } + + size_t size() const { return data.size(); } + const std::array& operator[](size_t idx) const + { + return data[idx]; + } + + } new_triangles; + + // A vector of bool for all faces signaling if it needs to be removed + // or not. + std::vector to_remove; + + MeshMods(const TriangleMesh &mesh): + to_remove(mesh.its.indices.size(), false) {} + + // Number of triangles that need to be removed. + size_t to_remove_cnt() const + { + return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0)); + } + + } mesh_mods{mesh}; + + // Must return true if further division of the face is needed. + auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) { + BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() }; + + // Face is certainly outside the cavity + if (! facebb.intersects(bb) && f.faceid != NEW_FACE) { + return false; + } + + TriangleBubble bubble{facebb.center().cast(), facebb.radius()}; + + double D = get_distance(bubble, interior); + double R = bubble.R * interior.voxel_scale; + + if (std::isnan(D)) // The distance cannot be measured, triangle too big + return true; + + // Distance of the bubble wall to the interior wall. Negative if the + // bubble is overlapping with the interior + double bubble_distance = D - R; + + // The face is crossing the interior or inside, it must be removed and + // parts of it re-added, that are outside the interior + if (bubble_distance < 0.) { + if (f.faceid != NEW_FACE) + mesh_mods.to_remove[f.faceid] = true; + + if (f.parent != NEW_FACE) // Top parent needs to be removed as well + mesh_mods.to_remove[f.parent] = true; + + // If the outside part is between the interior end the exterior + // (inside the wall being invisible), no further division is needed. + if ((R + D) < interior.thickness) + return false; + + return true; + } else if (f.faceid == NEW_FACE) { + // New face completely outside needs to be re-added. + mesh_mods.new_triangles.emplace_back(f.verts); + } + + return false; + }; + + interior.reset_accessor(); + + exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) { + const Vec3i &face = faces[face_idx]; + + std::array pts = + { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; + + BoundingBoxf3 facebb { pts.begin(), pts.end() }; + + // Face is certainly outside the cavity + if (! facebb.intersects(bb)) return; + + DivFace df{face, pts, long(face_idx)}; + + if (divfn(df)) + divide_triangle(df, divfn); + + }, exec_policy::max_concurreny()); + + auto new_faces = reserve_vector(faces.size() + + mesh_mods.new_triangles.size()); + + for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) { + if (!mesh_mods.to_remove[face_idx]) + new_faces.emplace_back(faces[face_idx]); + } + + for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) { + size_t o = vertices.size(); + vertices.emplace_back(mesh_mods.new_triangles[i][0]); + vertices.emplace_back(mesh_mods.new_triangles[i][1]); + vertices.emplace_back(mesh_mods.new_triangles[i][2]); + new_faces.emplace_back(int(o), int(o + 1), int(o + 2)); + } + + BOOST_LOG_TRIVIAL(info) + << "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed"; + BOOST_LOG_TRIVIAL(info) + << "Trimming: " << mesh_mods.new_triangles.size() << " triangles added"; + + faces.swap(new_faces); + new_faces = {}; + + mesh = TriangleMesh{mesh.its}; + mesh.repaired = true; + mesh.require_shared_vertices(); } }} // namespace Slic3r::sla From 4374716bfb45fc1dfa0af37bbc5836dce4da012a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 18:25:24 +0100 Subject: [PATCH 16/60] Triangle trimming should handle drilled meshes separately --- src/libslic3r/SLAPrintSteps.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 4637aa761..036fed171 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -169,10 +169,10 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; - mesh_view = po.transformed_mesh(); - sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); if (! needs_drilling) { + mesh_view = po.transformed_mesh(); + sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; return; } @@ -200,6 +200,9 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) try { MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); + + mesh_view = hollowed_mesh; + sla::remove_inside_triangles(mesh_view, *po.m_hollowing_data->interior); } catch (const std::runtime_error &) { throw Slic3r::SlicingError(L( "Drilling holes into the mesh failed. " From fbc758642bbfc8f5affc0dff24ce10cc6b383b2d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 18:48:24 +0100 Subject: [PATCH 17/60] Fix crash when the interior is corrupted --- src/libslic3r/SLAPrintSteps.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 036fed171..fe5d7505f 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -166,13 +166,18 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) // holes that are no longer on the frontend. TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; hollowed_mesh = po.transformed_mesh(); - sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); + if (is_hollowed) + sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; if (! needs_drilling) { mesh_view = po.transformed_mesh(); - sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); + + if (is_hollowed) + sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, + sla::hfRemoveInsideTriangles); + BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; return; } @@ -200,9 +205,13 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) try { MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); - mesh_view = hollowed_mesh; - sla::remove_inside_triangles(mesh_view, *po.m_hollowing_data->interior); + + if (is_hollowed) + sla::remove_inside_triangles(mesh_view, + *po.m_hollowing_data->interior, + drainholes); + } catch (const std::runtime_error &) { throw Slic3r::SlicingError(L( "Drilling holes into the mesh failed. " From a62262666a2d3ad304bcbe88a3971542e282ff1d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Mar 2021 09:29:13 +0100 Subject: [PATCH 18/60] Exclude triangles of original interior mesh and drillholes from trimming --- src/libslic3r/SLA/Hollowing.cpp | 12 ++- src/libslic3r/SLA/Hollowing.hpp | 11 ++- src/libslic3r/SLAPrintSteps.cpp | 143 +++++++++++++++++++++++++++++++- src/libslic3r/TriangleMesh.cpp | 18 ++++ src/libslic3r/TriangleMesh.hpp | 6 ++ 5 files changed, 184 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 632917266..b38784521 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -421,7 +421,8 @@ void divide_triangle(const DivFace &face, Fn &&visitor) divide_triangle(child2, std::forward(visitor)); } -void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask) { enum TrPos { posInside, posTouch, posOutside }; @@ -429,6 +430,11 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) auto &vertices = mesh.its.vertices; auto bb = mesh.bounding_box(); + bool use_exclude_mask = faces.size() == exclude_mask.size(); + auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) { + return use_exclude_mask && exclude_mask[face_id]; + }; + // TODO: Parallel mode not working yet using exec_policy = ccr_seq; @@ -518,6 +524,10 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) { const Vec3i &face = faces[face_idx]; + // If the triangle is excluded, we need to keep it. + if (is_excluded(face_idx)) + return; + std::array pts = { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index caa7d7b6b..5d2181e7a 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -81,7 +81,16 @@ void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); // Hollowing prepared in "interior", merge with original mesh void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); -void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior); +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask = {}); + +double get_distance(const Vec3f &p, const Interior &interior); + +template +FloatingOnly get_distance(const Vec<3, T> &p, const Interior &interior) +{ + return get_distance(Vec3f(p.template cast()), interior); +} void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index fe5d7505f..455141051 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -142,6 +144,136 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) } } + +struct FaceHash { + + // A hash is created for each triangle to be identifiable. The hash uses + // only the triangle's geometric traits, not the index in a particular mesh. + std::unordered_set facehash; + + static std::string facekey(const Vec3i &face, + const std::vector &vertices) + { + // Scale to integer to avoid floating points + std::array, 3> pts = { + scaled(vertices[face(0)]), + scaled(vertices[face(1)]), + scaled(vertices[face(2)]) + }; + + // Get the first two sides of the triangle, do a cross product and move + // that vector to the center of the triangle. This encodes all + // information to identify an identical triangle at the same position. + Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2]; + Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; + + // Return a concatenated string representation of the coordinates + return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2)); + }; + + FaceHash(const indexed_triangle_set &its) + { + for (const Vec3i &face : its.indices) { + std::string keystr = facekey(face, its.vertices); + facehash.insert(keystr); + } + } + + bool find(const std::string &key) + { + auto it = facehash.find(key); + return it != facehash.end(); + } +}; + +// Create exclude mask for triangle removal inside hollowed interiors. +// This is necessary when the interior is already part of the mesh which was +// drilled using CGAL mesh boolean operation. Excluded will be the triangles +// originally part of the interior mesh and triangles that make up the drilled +// hole walls. +static std::vector create_exclude_mask( + const indexed_triangle_set &its, + const sla::Interior &interior, + const std::vector &holes) +{ + FaceHash interior_hash{sla::get_mesh(interior).its}; + + std::vector exclude_mask(its.indices.size(), false); + + std::vector< std::vector > neighbor_index = + create_neighbor_index(its); + + auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face) + { + for (int i = 0; i < 3; ++i) { + const std::vector &neighbors = neighbor_index[face(i)]; + for (size_t fi_n : neighbors) exclude_mask[fi_n] = true; + } + }; + + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + + std::string key = + FaceHash::facekey(face, its.vertices); + + if (interior_hash.find(key)) { + exclude_mask[fi] = true; + continue; + } + + if (exclude_mask[fi]) { + exclude_neighbors(face); + continue; + } + + // Lets deal with the holes. All the triangles of a hole and all the + // neighbors of these triangles need to be kept. The neigbors were + // created by CGAL mesh boolean operation that modified the original + // interior inside the input mesh to contain the holes. + Vec3d tr_center = ( + its.vertices[face(0)] + + its.vertices[face(1)] + + its.vertices[face(2)] + ).cast() / 3.; + + // If the center is more than half a mm inside the interior, + // it cannot possibly be part of a hole wall. + if (sla::get_distance(tr_center, interior) < -0.5) + continue; + + Vec3f U = its.vertices[face(1)] - its.vertices[face(0)]; + Vec3f V = its.vertices[face(2)] - its.vertices[face(0)]; + Vec3f C = U.cross(V); + Vec3f face_normal = C.normalized(); + + for (const sla::DrainHole &dh : holes) { + Vec3d dhpos = dh.pos.cast(); + Vec3d dhend = dhpos + dh.normal.cast() * dh.height; + + Linef3 holeaxis{dhpos, dhend}; + + double D_hole_center = line_alg::distance_to(holeaxis, tr_center); + double D_hole = std::abs(D_hole_center - dh.radius); + float dot = dh.normal.dot(face_normal); + + // Empiric tolerances for center distance and normals angle. + // For triangles that are part of a hole wall the angle of + // triangle normal and the hole axis is around 90 degrees, + // so the dot product is around zero. + double D_tol = dh.radius / sla::DrainHole::steps; + float normal_angle_tol = 1.f / sla::DrainHole::steps; + + if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) { + exclude_mask[fi] = true; + exclude_neighbors(face); + } + } + } + + return exclude_mask; +} + // Drill holes into the hollowed/original mesh. void SLAPrint::Steps::drill_holes(SLAPrintObject &po) { @@ -207,10 +339,13 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); mesh_view = hollowed_mesh; - if (is_hollowed) - sla::remove_inside_triangles(mesh_view, - *po.m_hollowing_data->interior, - drainholes); + if (is_hollowed) { + auto &interior = *po.m_hollowing_data->interior; + std::vector exclude_mask = + create_exclude_mask(mesh_view.its, interior, drainholes); + + sla::remove_inside_triangles(mesh_view, interior, exclude_mask); + } } catch (const std::runtime_error &) { throw Slic3r::SlicingError(L( diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index adb9be64d..d5a349087 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa) return mesh; } +std::vector > create_neighbor_index(const indexed_triangle_set &its) +{ + if (its.vertices.empty()) return {}; + + size_t res = its.indices.size() / its.vertices.size(); + std::vector< std::vector > index(its.vertices.size(), + reserve_vector(res)); + + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + index[face(0)].emplace_back(fi); + index[face(1)].emplace_back(fi); + index[face(2)].emplace_back(fi); + } + + return index; +} + } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 9625298f4..e6f6dc84b 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -89,6 +89,12 @@ private: std::deque find_unvisited_neighbors(std::vector &facet_visited) const; }; +// Create an index of faces belonging to each vertex. The returned vector can +// be indexed with vertex indices and contains a list of face indices for each +// vertex. +std::vector< std::vector > +create_neighbor_index(const indexed_triangle_set &its); + enum FacetEdgeType { // A general case, the cutting plane intersect a face at two different edges. feGeneral, From 33d6655f26d70f23007afb26345dc302da894f23 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 19:35:46 +0100 Subject: [PATCH 19/60] Clean up hollowing test Needs rethinking anyway --- tests/libslic3r/test_hollowing.cpp | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index 2218a27b7..1f5ca3845 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -2,29 +2,7 @@ #include #include -#include #include "libslic3r/SLA/Hollowing.hpp" -#include -#include "libslic3r/Format/OBJ.hpp" - -#include - -#include - -#if defined(WIN32) || defined(_WIN32) -#define PATH_SEPARATOR R"(\)" -#else -#define PATH_SEPARATOR R"(/)" -#endif - -static Slic3r::TriangleMesh load_model(const std::string &obj_filename) -{ - Slic3r::TriangleMesh mesh; - auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename; - Slic3r::load_obj(fpath.c_str(), &mesh); - return mesh; -} - TEST_CASE("Hollow two overlapping spheres") { using namespace Slic3r; From 3c2d0b7c6e6668a148e309b2b39bd9d41acdd5d5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Mar 2021 10:04:26 +0100 Subject: [PATCH 20/60] Tiny cosmetics --- src/libslic3r/SLAPrintSteps.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 455141051..22fee6976 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -144,7 +144,6 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) } } - struct FaceHash { // A hash is created for each triangle to be identifiable. The hash uses @@ -214,10 +213,7 @@ static std::vector create_exclude_mask( for (size_t fi = 0; fi < its.indices.size(); ++fi) { auto &face = its.indices[fi]; - std::string key = - FaceHash::facekey(face, its.vertices); - - if (interior_hash.find(key)) { + if (interior_hash.find(FaceHash::facekey(face, its.vertices))) { exclude_mask[fi] = true; continue; } From 158413f4c4b9a127bea21b7ee7128afc579d80ae Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 9 Mar 2021 12:37:17 +0100 Subject: [PATCH 21/60] Disable wxMediaCtrl in wxWidgets build We don't need it. Building on Linux causes problems with gstreamer. fixes #5815, #6160 --- deps/wxWidgets/wxWidgets.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index c7facc2c6..0c8eaca97 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -20,6 +20,7 @@ prusaslicer_add_cmake_project(wxWidgets ${_wx_toolkit} "-DCMAKE_DEBUG_POSTFIX:STRING=" -DwxBUILD_DEBUG_LEVEL=0 + -DwxUSE_MEDIACTRL=OFF -DwxUSE_DETECT_SM=OFF -DwxUSE_UNICODE=ON -DwxUSE_OPENGL=ON From b8adfbda662af5a225cbd9fd594e8c3314691d45 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 11 Mar 2021 13:35:19 +0100 Subject: [PATCH 22/60] saving size and position of print host queue dialog, added size column, sorting --- src/slic3r/GUI/PrintHostDialogs.cpp | 104 ++++++++++++++++++++++++++-- src/slic3r/GUI/PrintHostDialogs.hpp | 18 ++++- 2 files changed, 114 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index cb4c22299..af98f3279 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -182,15 +182,24 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) auto *topsizer = new wxBoxSizer(wxVERTICAL); + std::vector widths; + widths.reserve(6); + if (!load_user_data(UDT_COLS, widths)) { + widths.clear(); + for (size_t i = 0; i < 6; i++) + widths.push_back(-1); + } + job_list = new wxDataViewListCtrl(this, wxID_ANY); // Note: Keep these in sync with Column - job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT); - job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT); - job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT); - job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT); - job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT); + 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); - + auto *btnsizer = new wxBoxSizer(wxHORIZONTAL); btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected")); btn_cancel->Disable(); @@ -207,7 +216,21 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) topsizer->Add(btnsizer, 0, wxEXPAND); SetSizer(topsizer); - SetSize(wxSize(HEIGHT * em, WIDTH * em)); + std::vector size; + SetSize(load_user_data(UDT_SIZE, size) ? wxSize(size[0] * em, size[1] * em) : wxSize(HEIGHT * em, WIDTH * em)); + + Bind(wxEVT_SIZE, [this, em](wxSizeEvent& evt) { + OnSize(evt); + save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); + }); + + std::vector pos; + if (load_user_data(UDT_POSITION, pos)) + SetPosition(wxPoint(pos[0], pos[1])); + + Bind(wxEVT_MOVE, [this, em](wxMoveEvent& evt) { + save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); + }); job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); }); @@ -238,6 +261,12 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) fields.push_back(wxVariant(0)); fields.push_back(wxVariant(_L("Enqueued"))); fields.push_back(wxVariant(job.printhost->get_host())); + boost::system::error_code ec; + boost::uintmax_t size_i = boost::filesystem::file_size(job.upload_data.source_path, ec); + std::string size = ec ? "unknown" : ((size_i >> 10) > 1024 ? std::to_string((float)(size_i >> 10)/1024) + "MB" : std::to_string(size_i >> 10) + "KB"); + if (ec) + BOOST_LOG_TRIVIAL(error) << ec.message(); + fields.push_back(wxVariant(size)); fields.push_back(wxVariant(job.upload_data.upload_path.string())); fields.push_back(wxVariant("")); job_list->AppendItem(fields, static_cast(ST_NEW)); @@ -255,6 +284,8 @@ void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect) Fit(); Refresh(); + + save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); } PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx) @@ -276,6 +307,8 @@ void PrintHostQueueDialog::set_state(int idx, JobState state) case ST_CANCELLED: job_list->SetValue(_L("Cancelled"), idx, COL_STATUS); break; case ST_COMPLETED: job_list->SetValue(_L("Completed"), idx, COL_STATUS); break; } + // This might be ambigous call, but user data needs to be saved time to time + save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); } void PrintHostQueueDialog::on_list_select() @@ -330,6 +363,7 @@ void PrintHostQueueDialog::on_cancel(Event &evt) on_list_select(); } + void PrintHostQueueDialog::get_active_jobs(std::vector>& ret) { int ic = job_list->GetItemCount(); @@ -343,4 +377,60 @@ void PrintHostQueueDialog::get_active_jobs(std::vectordata } +void PrintHostQueueDialog::save_user_data(int udt) +{ + const auto em = GetTextExtent("m").x; + BOOST_LOG_TRIVIAL(error) << "save" << this->GetSize().x / em << " " << this->GetSize().y / em << " " << this->GetPosition().x << " " << this->GetPosition().y; + auto *app_config = wxGetApp().app_config; + if (udt & UserDataType::UDT_SIZE) { + + app_config->set("print_host_queue_dialog_height", std::to_string(this->GetSize().x / em)); + app_config->set("print_host_queue_dialog_width", std::to_string(this->GetSize().y / em)); + } + if (udt & UserDataType::UDT_POSITION) + { + app_config->set("print_host_queue_dialog_x", std::to_string(this->GetPosition().x)); + app_config->set("print_host_queue_dialog_y", std::to_string(this->GetPosition().y)); + } + if (udt & UserDataType::UDT_COLS) + { + for (size_t i = 0; i < job_list->GetColumnCount() - 1; i++) + { + app_config->set("print_host_queue_dialog_column_" + std::to_string(i), std::to_string(job_list->GetColumn(i)->GetWidth())); + } + } +} +bool PrintHostQueueDialog::load_user_data(int udt, std::vector& vector) +{ + auto* app_config = wxGetApp().app_config; + auto hasget = [app_config](const std::string& name, std::vector& vector)->bool { + if (app_config->has(name)) { + vector.push_back(std::stoi(app_config->get(name))); + return true; + } + return false; + }; + if (udt & UserDataType::UDT_SIZE) { + if (!hasget("print_host_queue_dialog_height",vector)) + return false; + if (!hasget("print_host_queue_dialog_width", vector)) + return false; + } + if (udt & UserDataType::UDT_POSITION) + { + if (!hasget("print_host_queue_dialog_x", vector)) + return false; + if (!hasget("print_host_queue_dialog_y", vector)) + return false; + } + if (udt & UserDataType::UDT_COLS) + { + for (size_t i = 0; i < 6; i++) + { + if (!hasget("print_host_queue_dialog_column_" + std::to_string(i), vector)) + return false; + } + } + return true; +} }} diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index 1fda0db66..c185f57be 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -65,6 +65,13 @@ public: void append_job(const PrintHostJob &job); void get_active_jobs(std::vector>& ret); + + virtual bool Show(bool show = true) override + { + if(!show) + save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); + return DPIDialog::Show(show); + } protected: void on_dpi_changed(const wxRect &suggested_rect) override; @@ -74,8 +81,9 @@ private: COL_PROGRESS, COL_STATUS, COL_HOST, + COL_SIZE, COL_FILENAME, - COL_ERRORMSG, + COL_ERRORMSG }; enum JobState { @@ -89,6 +97,12 @@ private: enum { HEIGHT = 60, WIDTH = 30, SPACING = 5 }; + enum UserDataType{ + UDT_SIZE = 1, + UDT_POSITION = 2, + UDT_COLS = 4 + }; + wxButton *btn_cancel; wxButton *btn_error; wxDataViewListCtrl *job_list; @@ -105,6 +119,8 @@ private: void on_cancel(Event&); // This vector keep adress and filename of uploads. It is used when checking for running uploads during exit. std::vector> upload_names; + void save_user_data(int); + bool load_user_data(int, std::vector&); }; wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); From 58733e68075566062fe5623be5a847ede74d102d Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 11 Mar 2021 16:32:20 +0100 Subject: [PATCH 23/60] print host upload queue dialog - precision in size column --- src/slic3r/GUI/PrintHostDialogs.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index af98f3279..605e14349 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -263,10 +263,14 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) fields.push_back(wxVariant(job.printhost->get_host())); boost::system::error_code ec; boost::uintmax_t size_i = boost::filesystem::file_size(job.upload_data.source_path, ec); - std::string size = ec ? "unknown" : ((size_i >> 10) > 1024 ? std::to_string((float)(size_i >> 10)/1024) + "MB" : std::to_string(size_i >> 10) + "KB"); - if (ec) + std::stringstream stream; + if (ec) { + stream << "unknown"; + size_i = 0; BOOST_LOG_TRIVIAL(error) << ec.message(); - fields.push_back(wxVariant(size)); + } else + stream << std::fixed << std::setprecision(2) << ((float)size_i / 1024 / 1024) << "MB"; + fields.push_back(wxVariant(stream.str())); fields.push_back(wxVariant(job.upload_data.upload_path.string())); fields.push_back(wxVariant("")); job_list->AppendItem(fields, static_cast(ST_NEW)); From 08a826d2372be9c9c8b0ca4f473f3b27c2dee4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 12 Mar 2021 10:30:06 +0100 Subject: [PATCH 24/60] Added a missing includes --- src/slic3r/GUI/PrintHostDialogs.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 605e14349..a094b70e9 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -1,6 +1,7 @@ #include "PrintHostDialogs.hpp" #include +#include #include #include @@ -13,6 +14,9 @@ #include #include +#include +#include + #include "GUI.hpp" #include "GUI_App.hpp" #include "MsgDialog.hpp" From c41df487bb5821a9271e547ab97d424e1e9b6afa Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 8 Feb 2021 16:14:35 +0100 Subject: [PATCH 25/60] Notifications management and rendering refactoring. With warning notification Model out of bed reworked to not show after dismiss. --- src/slic3r/GUI/3DScene.cpp | 51 ++++ src/slic3r/GUI/3DScene.hpp | 1 + src/slic3r/GUI/GLCanvas3D.cpp | 65 +++-- src/slic3r/GUI/GLCanvas3D.hpp | 3 +- src/slic3r/GUI/NotificationManager.cpp | 385 ++++++++++--------------- src/slic3r/GUI/NotificationManager.hpp | 112 ++++--- 6 files changed, 293 insertions(+), 324 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 4cab8f3db..6c226cd74 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -845,6 +845,57 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M return contained_min_one; } +bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) +{ + if (config == nullptr) + return false; + + const ConfigOptionPoints* opt = dynamic_cast(config->option("bed_shape")); + if (opt == nullptr) + return false; + + BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); + BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Vec3d(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height"))); + // Allow the objects to protrude below the print bed + print_volume.min(2) = -1e10; + print_volume.min(0) -= BedEpsilon; + print_volume.min(1) -= BedEpsilon; + print_volume.max(0) += BedEpsilon; + print_volume.max(1) += BedEpsilon; + + bool contained_min_one = false; + + partlyOut = false; + fullyOut = false; + for (GLVolume* volume : this->volumes) + { + if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) + continue; + + const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); + bool contained = print_volume.contains(bb); + + volume->is_outside = !contained; + if (!volume->printable) + continue; + + if (contained) + contained_min_one = true; + + if (volume->is_outside) { + if (print_volume.intersects(bb)) + partlyOut = true; + else + fullyOut = true; + } + } + /* + if (out_state != nullptr) + *out_state = state; + */ + return contained_min_one; +} + void GLVolumeCollection::reset_outside_state() { for (GLVolume* volume : this->volumes) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index e4d9c6067..2ae2a36b2 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -569,6 +569,7 @@ public: // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state); + bool check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut); void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a45f61cab..0a2b5cd65 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -641,6 +641,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool error = true; break; } + BOOST_LOG_TRIVIAL(error) << state << " : " << text ; auto ¬ification_manager = *wxGetApp().plater()->get_notification_manager(); if (state) { if(error) @@ -1620,9 +1621,6 @@ void GLCanvas3D::render() wxGetApp().plater()->init_environment_texture(); #endif // ENABLE_ENVIRONMENT_MAP - m_render_timer.Stop(); - m_extra_frame_requested_delayed = std::numeric_limits::max(); - const Size& cnv_size = get_canvas_size(); // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene // to preview, this was called before canvas had its final size. It reported zero width @@ -1754,7 +1752,7 @@ void GLCanvas3D::render() m_tooltip.render(m_mouse.position, *this); wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); - wxGetApp().plater()->get_notification_manager()->render_notifications(get_overlay_window_width()); + wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); wxGetApp().imgui()->render(); @@ -2238,24 +2236,24 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { - ModelInstanceEPrintVolumeState state; - - const bool contained_min_one = m_volumes.check_outside_state(m_config, &state); + bool partlyOut = false; + bool fullyOut = false; + const bool contained_min_one = m_volumes.check_outside_state(m_config, partlyOut, fullyOut); #if ENABLE_WARNING_TEXTURE_REMOVAL - _set_warning_notification(EWarning::ObjectClashed, state == ModelInstancePVS_Partly_Outside); - _set_warning_notification(EWarning::ObjectOutside, state == ModelInstancePVS_Fully_Outside); - if (printer_technology != ptSLA || state == ModelInstancePVS_Inside) + _set_warning_notification(EWarning::ObjectClashed, partlyOut); + _set_warning_notification(EWarning::ObjectOutside, fullyOut); + if (printer_technology != ptSLA || !contained_min_one) _set_warning_notification(EWarning::SlaSupportsOutside, false); #else - _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstancePVS_Partly_Outside); - _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstancePVS_Fully_Outside); - if(printer_technology != ptSLA || state == ModelInstancePVS_Inside) + _set_warning_texture(WarningTexture::ObjectClashed, partlyOut); + _set_warning_texture(WarningTexture::ObjectOutside, fullyOut); + if(printer_technology != ptSLA || !contained_min_one) _set_warning_texture(WarningTexture::SlaSupportsOutside, false); #endif // ENABLE_WARNING_TEXTURE_REMOVAL post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, - contained_min_one && !m_model->objects.empty() && state != ModelInstancePVS_Partly_Outside)); + contained_min_one && !m_model->objects.empty() && !partlyOut)); } else { #if ENABLE_WARNING_TEXTURE_REMOVAL @@ -2442,13 +2440,13 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) if (!m_initialized) return; - // FIXME m_dirty |= m_main_toolbar.update_items_state(); m_dirty |= m_undoredo_toolbar.update_items_state(); m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); m_dirty |= mouse3d_controller_applied; + m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); if (!m_dirty) return; @@ -2982,30 +2980,39 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt) void GLCanvas3D::on_render_timer(wxTimerEvent& evt) { - // If slicer is not top window -> restart timer with one second to try again - wxWindow* p = dynamic_cast(wxGetApp().plater()); - while (p->GetParent() != nullptr) - p = p->GetParent(); - wxTopLevelWindow* top_level_wnd = dynamic_cast(p); - if (!top_level_wnd->IsActive()) { - request_extra_frame_delayed(1000); - return; - } - //render(); - m_dirty = true; - wxWakeUpIdle(); + // no need to do anything here + // right after this event is recieved, idle event is fired + + //m_dirty = true; + //wxWakeUpIdle(); } -void GLCanvas3D::request_extra_frame_delayed(int miliseconds) + +void GLCanvas3D::schedule_extra_frame(int miliseconds) { + // Schedule idle event right now + if (miliseconds == 0) + { + // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait + if (m_in_render) + miliseconds = 33; + else { + m_dirty = true; + wxWakeUpIdle(); + return; + } + } + // Start timer int64_t now = timestamp_now(); + // Timer is not running if (! m_render_timer.IsRunning()) { m_extra_frame_requested_delayed = miliseconds; m_render_timer.StartOnce(miliseconds); m_render_timer_start = now; + // Timer is running - restart only if new period is shorter than remaning period } else { const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now; - if (miliseconds < remaining_time) { + if (miliseconds + 20 < remaining_time) { m_render_timer.Stop(); m_extra_frame_requested_delayed = miliseconds; m_render_timer.StartOnce(miliseconds); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 10294931f..f4d862b66 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -743,7 +743,8 @@ public: void msw_rescale(); void request_extra_frame() { m_extra_frame_requested = true; } - void request_extra_frame_delayed(int miliseconds); + + void schedule_extra_frame(int miliseconds); int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); } diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index a6fd9cfd3..41c8e8f6f 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -22,9 +22,6 @@ static constexpr float SPACE_RIGHT_PANEL = 10.0f; static constexpr float FADING_OUT_DURATION = 2.0f; // Time in Miliseconds after next render when fading out is requested static constexpr int FADING_OUT_TIMEOUT = 100; -// If timeout is changed to higher than 1 second, substract_time call should be revorked -//static constexpr int MAX_TIMEOUT_MILISECONDS = 1000; -//static constexpr int MAX_TIMEOUT_SECONDS = 1; namespace Slic3r { namespace GUI { @@ -131,35 +128,29 @@ void NotificationManager::NotificationIDProvider::release_id(int) {} NotificationManager::PopNotification::PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler) : m_data (n) , m_id_provider (id_provider) - , m_remaining_time (n.duration) - , m_last_remaining_time (n.duration) - , m_counting_down (n.duration != 0) , m_text1 (n.text1) , m_hypertext (n.hypertext) , m_text2 (n.text2) , m_evt_handler (evt_handler) , m_notification_start (GLCanvas3D::timestamp_now()) -{ - //init(); -} +{} void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) { - if (!m_initialized) + + if (m_state == EState::Unknown) init(); - if (m_hidden) { + if (m_state == EState::Hidden) { m_top_y = initial_y - GAP_WIDTH; return; } - if (m_fading_out) - m_last_render_fading = GLCanvas3D::timestamp_now(); - - Size cnv_size = canvas.get_canvas_size(); + Size cnv_size = canvas.get_canvas_size(); ImGuiWrapper& imgui = *wxGetApp().imgui(); - ImVec2 mouse_pos = ImGui::GetMousePos(); - float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); + ImVec2 mouse_pos = ImGui::GetMousePos(); + float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); + bool fading_pop = false; if (m_line_height != ImGui::CalcTextSize("A").y) init(); @@ -174,54 +165,46 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); // find if hovered - m_hovered = false; + if (m_state == EState::Hovered) + m_state = EState::Shown; + if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { ImGui::SetNextWindowFocus(); - m_hovered = true; + m_state = EState::Hovered; } - + // color change based on fading out - bool fading_pop = false; - if (m_fading_out) { - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); + if (m_state == EState::FadingOut) { + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), true, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), true, m_current_fade_opacity); fading_pop = true; } - + // background color if (m_is_gray) { ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); } else if (m_data.level == NotificationLevel::ErrorNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); } else if (m_data.level == NotificationLevel::WarningNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; backcolor.y += 0.15f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); } - - // name of window - probably indentifies window and is shown so last_end add whitespaces according to id + + // name of window indentifies window - has to be unique string if (m_id == 0) m_id = m_id_provider.allocate_id(); std::string name = "!!Ntfctn" + std::to_string(m_id); + if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar)) { ImVec2 win_size = ImGui::GetWindowSize(); - //FIXME: dont forget to us this for texts - //GUI::format(_utf8(L())); - - /* - //countdown numbers - ImGui::SetCursorPosX(15); - ImGui::SetCursorPosY(15); - imgui.text(std::to_string(m_remaining_time).c_str()); - */ - render_left_sign(imgui); render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); @@ -253,6 +236,7 @@ void NotificationManager::PopNotification::count_spaces() m_window_width_offset = m_left_indentation + m_line_height * 3.f; m_window_width = m_line_height * 25; } + void NotificationManager::PopNotification::init() { std::string text = m_text1 + " " + m_hypertext; @@ -306,7 +290,7 @@ void NotificationManager::PopNotification::init() } if (m_lines_count == 3) m_multiline = true; - m_initialized = true; + m_state = EState::Shown; } void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) { @@ -423,8 +407,8 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, m_multiline = true; set_next_window_size(imgui); } - else { - m_close_pending = on_text_click(); + else if (on_text_click()) { + close(); } } ImGui::PopStyleColor(); @@ -432,12 +416,12 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, ImGui::PopStyleColor(); //hover color - ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);//ImGui::GetStyleColorVec4(ImGuiCol_Button); + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) orange_color.y += 0.2f; //text - Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_state == EState::FadingOut, m_current_fade_opacity); ImGui::SetCursorPosX(text_x); ImGui::SetCursorPosY(text_y); imgui.text(text.c_str()); @@ -448,7 +432,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, lineEnd.y -= 2; ImVec2 lineStart = lineEnd; lineStart.x = ImGui::GetItemRectMin().x; - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f)))); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f)))); } @@ -458,12 +442,11 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); - //button - if part if treggered std::string button_text; button_text = ImGui::CloseNotifButton; @@ -479,7 +462,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - m_close_pending = true; + close(); } //invisible large button @@ -487,7 +470,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImGui::SetCursorPosY(0); if (imgui.button(" ", m_line_height * 2.125, win_size.y - ( m_minimize_b_visible ? 2 * m_line_height : 0))) { - m_close_pending = true; + close(); } ImGui::PopStyleColor(); ImGui::PopStyleColor(); @@ -510,9 +493,9 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); //button - if part if treggered @@ -564,71 +547,56 @@ bool NotificationManager::PopNotification::compare_text(const std::string& text) return false; } -void NotificationManager::PopNotification::update_state() +void NotificationManager::PopNotification::update_state(bool paused, const int64_t delta) { - if (!m_initialized) + if (m_state == EState::Unknown) init(); m_next_render = std::numeric_limits::max(); - if (m_hidden) { - m_state = EState::Hidden; + if (m_state == EState::Hidden) { return; } int64_t now = GLCanvas3D::timestamp_now(); - if (m_hovered) { - // reset fading - m_fading_out = false; + // reset timers - hovered state is set in render + if (m_state == EState::Hovered) { m_current_fade_opacity = 1.0f; - m_remaining_time = m_data.duration; m_notification_start = now; - } - - - - if (m_counting_down) { + // Timers when not fading + } else if (m_data.duration != 0 && !paused) { int64_t up_time = now - m_notification_start; - - if (m_fading_out && m_current_fade_opacity <= 0.0f) - m_finished = true; - else if (!m_fading_out && /*m_remaining_time <=0*/up_time >= m_data.duration * 1000) { - m_fading_out = true; - m_fading_start = now; - m_last_render_fading = now; - } else if (!m_fading_out) { - m_next_render = m_data.duration * 1000 - up_time;//std::min(/*m_data.duration * 1000 - up_time*/m_remaining_time * 1000, MAX_TIMEOUT_MILISECONDS); - } - + if (m_state != EState::FadingOut && up_time >= m_data.duration * 1000) { + m_state = EState::FadingOut; + m_fading_start = now; + } else if (m_state != EState::FadingOut) { + m_next_render = m_data.duration * 1000 - up_time; + } } - - if (m_finished) { + // Timers when fading + if (m_state == EState::FadingOut && !paused) { + int64_t curr_time = now - m_fading_start; + int64_t next_render = FADING_OUT_TIMEOUT - delta; + m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); + if (m_current_fade_opacity <= 0.0f) + m_state = EState::Finished; + else if (next_render < 0) + m_next_render = 0; + else + m_next_render = next_render; + } + + if (m_state == EState::Finished) { + m_next_render = 0; + return; + } + + if (m_state == EState::ClosePending) { m_state = EState::Finished; m_next_render = 0; return; } - if (m_close_pending) { - m_finished = true; - m_state = EState::ClosePending; - m_next_render = 0; - return; - } - if (m_fading_out) { - if (!m_paused) { - m_state = EState::FadingOutStatic; - int64_t curr_time = now - m_fading_start; - int64_t no_render_time = now - m_last_render_fading; - m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); - auto next_render = FADING_OUT_TIMEOUT - no_render_time; - if (next_render <= 0) { - //m_last_render_fading = GLCanvas3D::timestamp_now(); - m_state = EState::FadingOutRender; - m_next_render = 0; - } else - m_next_render = next_render; - } - } } NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : @@ -672,9 +640,10 @@ void NotificationManager::SlicingCompleteLargeNotification::set_print_info(const void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) { m_is_large = l; - m_counting_down = !l; + //FIXME this information should not be lost (change m_data?) +// m_counting_down = !l; m_hypertext = l ? _u8L("Export G-Code.") : std::string(); - m_hidden = !l; + m_state = l ? EState::Shown : EState::Hidden; } //---------------ExportFinishedNotification----------- void NotificationManager::ExportFinishedNotification::count_spaces() @@ -733,8 +702,8 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::string button_text; @@ -768,7 +737,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW assert(m_evt_handler != nullptr); if (m_evt_handler != nullptr) wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); - m_close_pending = true; + close(); } //invisible large button @@ -779,7 +748,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW assert(m_evt_handler != nullptr); if (m_evt_handler != nullptr) wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); - m_close_pending = true; + close(); } ImGui::PopStyleColor(); ImGui::PopStyleColor(); @@ -807,22 +776,12 @@ void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& img void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); - float invisible_length = 0;//((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); - //invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); + float invisible_length = 0; + ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length - m_window_width_offset, win_pos_y + win_size_y/2 + m_line_height / 2); ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y/2 + m_line_height / 2); ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); - /* - //countdown line - ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); - float invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); - invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); - ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f); - if (!m_paused) - m_countdown_frame++; - */ + } //------NotificationManager-------- NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : @@ -881,12 +840,7 @@ void NotificationManager::push_plater_error_notification(const std::string& text { push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); } -void NotificationManager::push_plater_warning_notification(const std::string& text) -{ - push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, 0); - // dissaper if in preview - set_in_preview(m_in_preview); -} + void NotificationManager::close_plater_error_notification(const std::string& text) { for (std::unique_ptr ¬ification : m_pop_notifications) { @@ -895,11 +849,32 @@ void NotificationManager::close_plater_error_notification(const std::string& tex } } } + +void NotificationManager::push_plater_warning_notification(const std::string& text) +{ + // Find if was not hidden + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { + if (notification->get_state() == PopNotification::EState::Hidden) { + //dynamic_cast(notification.get())->show(); + return; + } + } + } + + NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + push_notification_data(std::move(notification), 0); + // dissaper if in preview + set_in_preview(m_in_preview); +} + void NotificationManager::close_plater_warning_notification(const std::string& text) { for (std::unique_ptr ¬ification : m_pop_notifications) { if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { - notification->close(); + dynamic_cast(notification.get())->real_close(); } } } @@ -1008,7 +983,8 @@ void NotificationManager::set_progress_bar_percentage(const std::string& text, f for (std::unique_ptr& notification : m_pop_notifications) { if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { dynamic_cast(notification.get())->set_percentage(percentage); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + // FIX ME: this is massive gpu eater (render every frame) + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); found = true; } } @@ -1035,20 +1011,19 @@ bool NotificationManager::push_notification_data(std::unique_ptractivate_existing(notification.get())) { m_pop_notifications.back()->update(notification->get_data()); - canvas.request_extra_frame_delayed(33); + canvas.schedule_extra_frame(0); return false; } else { m_pop_notifications.emplace_back(std::move(notification)); - canvas.request_extra_frame_delayed(33); + canvas.schedule_extra_frame(0); return true; } } -void NotificationManager::render_notifications(float overlay_width) +void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay_width) { sort_notifications(); - - GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); + float last_y = 0.0f; for (const auto& notification : m_pop_notifications) { @@ -1059,9 +1034,54 @@ void NotificationManager::render_notifications(float overlay_width) } } - update_notifications(); + m_last_render = GLCanvas3D::timestamp_now(); } +bool NotificationManager::update_notifications(GLCanvas3D& canvas) +{ + // no update if not top window + wxWindow* p = dynamic_cast(wxGetApp().plater()); + while (p->GetParent() != nullptr) + p = p->GetParent(); + wxTopLevelWindow* top_level_wnd = dynamic_cast(p); + if (!top_level_wnd->IsActive()) + return false; + + // next_render() returns numeric_limits::max if no need for frame + const int64_t max = std::numeric_limits::max(); + int64_t next_render = max; + const int64_t time_since_render = GLCanvas3D::timestamp_now() - m_last_render; + // During render, each notification detects if its currently hovered and changes its state to EState::Hovered + // If any notification is hovered, all restarts its countdown + bool hover = false; + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->is_hovered()) { + hover = true; + break; + } + } + // update state of all notif and erase finished + for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { + std::unique_ptr& notification = *it; + notification->update_state(hover, time_since_render); + next_render = std::min(next_render, notification->next_render()); + if (notification->get_state() == PopNotification::EState::Finished) + it = m_pop_notifications.erase(it); + else + ++it; + } + + // render needed right now + if (next_render < 20) + return true; + // request next frame + if (next_render < max) + canvas.schedule_extra_frame(int(next_render)); + + return false; +} +>>>>>>> 6df0d8ff81... Notifications management and rendering refactoring. + void NotificationManager::sort_notifications() { // Stable sorting, so that the order of equal ranges is stable. @@ -1112,103 +1132,6 @@ void NotificationManager::set_in_preview(bool preview) } } -void NotificationManager::update_notifications() -{ - // no update if not top window - wxWindow* p = dynamic_cast(wxGetApp().plater()); - while (p->GetParent() != nullptr) - p = p->GetParent(); - wxTopLevelWindow* top_level_wnd = dynamic_cast(p); - if (!top_level_wnd->IsActive()) - return; - - //static size_t last_size = m_pop_notifications.size(); - - //request frames - int64_t next_render = std::numeric_limits::max(); - for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { - std::unique_ptr& notification = *it; - notification->set_paused(m_hovered); - notification->update_state(); - next_render = std::min(next_render, notification->next_render()); - if (notification->get_state() == PopNotification::EState::Finished) - it = m_pop_notifications.erase(it); - else { - - ++it; - } - } - /* - m_requires_update = false; - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->requires_update()) { - m_requires_update = true; - break; - } - } - */ - // update hovering state - m_hovered = false; - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->is_hovered()) { - m_hovered = true; - break; - } - } - - /* - // Reuire render if some notification was just deleted. - size_t curr_size = m_pop_notifications.size(); - m_requires_render = m_hovered || (last_size != curr_size); - last_size = curr_size; - - // Ask notification if it needs render - if (!m_requires_render) { - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->requires_render()) { - m_requires_render = true; - break; - } - } - } - // Make sure there will be update after last notification erased - if (m_requires_render) - m_requires_update = true; - */ - - - if (next_render == 0) - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(33); //few milliseconds to get from GLCanvas::render - else if (next_render < std::numeric_limits::max()) - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(int(next_render)); - - /* - // actualizate timers - wxWindow* p = dynamic_cast(wxGetApp().plater()); - while (p->GetParent() != nullptr) - p = p->GetParent(); - wxTopLevelWindow* top_level_wnd = dynamic_cast(p); - if (!top_level_wnd->IsActive()) - return; - - { - // Control the fade-out. - // time in seconds - long now = wxGetLocalTime(); - // Pausing fade-out when the mouse is over some notification. - if (!m_hovered && m_last_time < now) { - if (now - m_last_time >= MAX_TIMEOUT_SECONDS) { - for (auto& notification : m_pop_notifications) { - //if (notification->get_state() != PopNotification::EState::Static) - notification->substract_remaining_time(MAX_TIMEOUT_SECONDS); - } - m_last_time = now; - } - } - } - */ -} - bool NotificationManager::has_slicing_error_notification() { return std::any_of(m_pop_notifications.begin(), m_pop_notifications.end(), [](auto &n) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 62c4ea845..4c584d366 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -147,14 +147,15 @@ public: // finds ExportFinished notification and closes it if it was to removable device void device_ejected(); // renders notifications in queue and deletes expired ones - void render_notifications(float overlay_width); + void render_notifications(GLCanvas3D& canvas, float overlay_width); // finds and closes all notifications of given type void close_notification_of_type(const NotificationType type); // Which view is active? Plater or G-code preview? Hide warnings in G-code preview. void set_in_preview(bool preview); // Move to left to avoid colision with variable layer height gizmo. void set_move_from_overlay(bool move) { m_move_from_overlay = move; } - + // perform update_state on each notification and ask for more frames if needed, return true for render needed + bool update_notifications(GLCanvas3D& canvas); private: // duration 0 means not disapearing struct NotificationData { @@ -192,23 +193,24 @@ private: enum class EState { - Unknown, + Unknown, // NOT initialized Hidden, - FadingOutRender, // Requesting Render - FadingOutStatic, + Shown, // Requesting Render at some time if duration != 0 + FadingOut, // Requesting Render at some time ClosePending, // Requesting Render Finished, // Requesting Render + Hovered, // Followed by Shown + Paused }; PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler); virtual ~PopNotification() { if (m_id) m_id_provider.release_id(m_id); } void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); // close will dissapear notification on next render - void close() { m_close_pending = true; } + virtual void close() { m_state = EState::ClosePending; } // data from newer notification of same type void update(const NotificationData& n); - bool is_finished() const { return m_finished || m_close_pending; } - bool is_hovered() const { return m_hovered; } + bool is_finished() const { return m_state == EState::ClosePending || m_state == EState::Finished; } // returns top after movement float get_top() const { return m_top_y; } //returns top in actual frame @@ -216,21 +218,15 @@ private: const NotificationType get_type() const { return m_data.type; } const NotificationData get_data() const { return m_data; } const bool is_gray() const { return m_is_gray; } - // Call equals one second down - void substract_remaining_time(int seconds) { m_remaining_time -= seconds; } void set_gray(bool g) { m_is_gray = g; } - void set_paused(bool p) { m_paused = p; } bool compare_text(const std::string& text); - void hide(bool h) { m_hidden = h; } - // sets m_next_render with time of next mandatory rendering - void update_state(); - int64_t next_render() const { return m_next_render; } - /* - bool requires_render() const { return m_state == EState::FadingOutRender || m_state == EState::ClosePending || m_state == EState::Finished; } - bool requires_update() const { return m_state != EState::Hidden; } - */ - EState get_state() const { return m_state; } - protected: + void hide(bool h) { m_state = h ? EState::Hidden : EState::Unknown; } + // sets m_next_render with time of next mandatory rendering. Delta is time since last render. + void update_state(bool paused, const int64_t delta); + int64_t next_render() const { return is_finished() ? 0 : m_next_render; } + EState get_state() const { return m_state; } + bool is_hovered() const { return m_state == EState::Hovered; } + // Call after every size change void init(); // Part of init() @@ -254,45 +250,36 @@ private: // Hypertext action, returns true if notification should close. // Action is stored in NotificationData::callback as std::function virtual bool on_text_click(); - + protected: const NotificationData m_data; - // For reusing ImGUI windows. NotificationIDProvider &m_id_provider; + int m_id{ 0 }; + // State for rendering EState m_state { EState::Unknown }; - int m_id { 0 }; - bool m_initialized { false }; + // Time values for rendering fade-out + + int64_t m_fading_start{ 0LL }; + + // first appereance of notification or last hover; + int64_t m_notification_start; + // time to next must-do render + int64_t m_next_render{ std::numeric_limits::max() }; + float m_current_fade_opacity{ 1.0f }; + + // Notification data + // Main text std::string m_text1; // Clickable text std::string m_hypertext; // Aditional text after hypertext - currently not used std::string m_text2; - // Countdown variables - long m_remaining_time; - bool m_counting_down; - long m_last_remaining_time; - bool m_paused { false }; - int m_countdown_frame { 0 }; - bool m_fading_out { false }; - int64_t m_fading_start { 0LL }; - // time of last done render when fading - int64_t m_last_render_fading { 0LL }; - // first appereance of notification or last hover; - int64_t m_notification_start; - // time to next must-do render - int64_t m_next_render { std::numeric_limits::max() }; - float m_current_fade_opacity { 1.0f }; - // If hidden the notif is alive but not visible to user - bool m_hidden { false }; - // m_finished = true - does not render, marked to delete - bool m_finished { false }; - // Will go to m_finished next render - bool m_close_pending { false }; - bool m_hovered { false }; - // variables to count positions correctly + + // inner variables to position notification window, texts and buttons correctly + // all space without text float m_window_width_offset; // Space on left side without text @@ -302,9 +289,7 @@ private: float m_window_width { 450.0f }; //Distance from bottom of notifications to top of this notification float m_top_y { 0.0f }; - - // Height of text - // Used as basic scaling unit! + // Height of text - Used as basic scaling unit! float m_line_height; std::vector m_endlines; // Gray are f.e. eorrors when its uknown if they are still valid @@ -344,6 +329,15 @@ private: int warning_step; }; + class PlaterWarningNotification : public PopNotification + { + public: + PlaterWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {} + virtual void close() { m_state = EState::Hidden; } + void real_close() { m_state = EState::ClosePending; } + void show() { m_state = EState::Unknown; } + }; + class ProgressBarNotification : public PopNotification { public: @@ -405,33 +399,25 @@ private: void sort_notifications(); // If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed. bool has_slicing_error_notification(); - // perform update_state on each notification and ask for more frames if needed - void update_notifications(); - + // Target for wxWidgets events sent by clicking on the hyperlink available at some notifications. wxEvtHandler* m_evt_handler; // Cache of IDs to identify and reuse ImGUI windows. NotificationIDProvider m_id_provider; std::deque> m_pop_notifications; - // Last render time in seconds for fade out control. - long m_last_time { 0 }; - // When mouse hovers over some notification, the fade-out of all notifications is suppressed. - bool m_hovered { false }; //timestamps used for slicing finished - notification could be gone so it needs to be stored here std::unordered_set m_used_timestamps; // True if G-code preview is active. False if the Plater is active. bool m_in_preview { false }; // True if the layer editing is enabled in Plater, so that the notifications are shifted left of it. bool m_move_from_overlay { false }; + // Timestamp of last rendering + int64_t m_last_render { 0LL }; //prepared (basic) notifications const std::vector basic_notifications = { -// {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")}, -// {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") }, {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, -// {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") }, -// {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") }, - {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), + {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 10, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr) { if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); From 32dd1f6e7c3e2ebbf9fc111d2b428a165c1227ee Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 8 Feb 2021 16:22:52 +0100 Subject: [PATCH 26/60] notification time correction --- src/slic3r/GUI/NotificationManager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 4c584d366..63492e61c 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -417,7 +417,7 @@ private: //prepared (basic) notifications const std::vector basic_notifications = { {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, - {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 10, _u8L("Configuration update is available."), _u8L("See more."), + {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr) { if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); From 80f0d305c18a5e562631d32592b19ff2f2d3d518 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 8 Feb 2021 17:42:20 +0100 Subject: [PATCH 27/60] request frame change in notification --- src/slic3r/GUI/NotificationManager.cpp | 38 ++++++++++++++++---------- src/slic3r/GUI/NotificationManager.hpp | 3 +- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 41c8e8f6f..ab2e46650 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -547,15 +547,17 @@ bool NotificationManager::PopNotification::compare_text(const std::string& text) return false; } -void NotificationManager::PopNotification::update_state(bool paused, const int64_t delta) +bool NotificationManager::PopNotification::update_state(bool paused, const int64_t delta) { - if (m_state == EState::Unknown) + if (m_state == EState::Unknown) { init(); + return true; + } m_next_render = std::numeric_limits::max(); if (m_state == EState::Hidden) { - return; + return false; } int64_t now = GLCanvas3D::timestamp_now(); @@ -581,22 +583,25 @@ void NotificationManager::PopNotification::update_state(bool paused, const int64 m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); if (m_current_fade_opacity <= 0.0f) m_state = EState::Finished; - else if (next_render < 0) - m_next_render = 0; + else if (next_render <= 20) { + m_next_render = FADING_OUT_TIMEOUT; + return true; + } else m_next_render = next_render; } if (m_state == EState::Finished) { - m_next_render = 0; - return; + //m_next_render = 0; + return true; } if (m_state == EState::ClosePending) { m_state = EState::Finished; - m_next_render = 0; - return; + //m_next_render = 0; + return true; } + return false; } NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : @@ -1034,6 +1039,7 @@ void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay } } + BOOST_LOG_TRIVIAL(error) << "render " << GLCanvas3D::timestamp_now() - m_last_render; m_last_render = GLCanvas3D::timestamp_now(); } @@ -1051,6 +1057,7 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas) const int64_t max = std::numeric_limits::max(); int64_t next_render = max; const int64_t time_since_render = GLCanvas3D::timestamp_now() - m_last_render; + bool request_render = false; // During render, each notification detects if its currently hovered and changes its state to EState::Hovered // If any notification is hovered, all restarts its countdown bool hover = false; @@ -1063,7 +1070,7 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas) // update state of all notif and erase finished for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { std::unique_ptr& notification = *it; - notification->update_state(hover, time_since_render); + request_render |= notification->update_state(hover, time_since_render); next_render = std::min(next_render, notification->next_render()); if (notification->get_state() == PopNotification::EState::Finished) it = m_pop_notifications.erase(it); @@ -1071,16 +1078,19 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas) ++it; } + BOOST_LOG_TRIVIAL(error) << "update " << request_render << " : " << next_render <<" : " << GLCanvas3D::timestamp_now() - m_last_update; + m_last_update = GLCanvas3D::timestamp_now(); + //BOOST_LOG_TRIVIAL(error) << time_since_render << ":" << next_render; + // render needed right now - if (next_render < 20) - return true; + //if (next_render < 20) + // request_render = true; // request next frame if (next_render < max) canvas.schedule_extra_frame(int(next_render)); - return false; + return request_render; } ->>>>>>> 6df0d8ff81... Notifications management and rendering refactoring. void NotificationManager::sort_notifications() { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 63492e61c..0d1cd9996 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -222,7 +222,7 @@ private: bool compare_text(const std::string& text); void hide(bool h) { m_state = h ? EState::Hidden : EState::Unknown; } // sets m_next_render with time of next mandatory rendering. Delta is time since last render. - void update_state(bool paused, const int64_t delta); + bool update_state(bool paused, const int64_t delta); int64_t next_render() const { return is_finished() ? 0 : m_next_render; } EState get_state() const { return m_state; } bool is_hovered() const { return m_state == EState::Hovered; } @@ -413,6 +413,7 @@ private: bool m_move_from_overlay { false }; // Timestamp of last rendering int64_t m_last_render { 0LL }; + int64_t m_last_update { 0LL }; //prepared (basic) notifications const std::vector basic_notifications = { From bad12b5683015a58a17d634717143dec0adca0a1 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 8 Feb 2021 19:12:32 +0100 Subject: [PATCH 28/60] cleanup --- src/slic3r/GUI/NotificationManager.cpp | 5 ----- src/slic3r/GUI/NotificationManager.hpp | 1 - 2 files changed, 6 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index ab2e46650..28feb65d0 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1039,7 +1039,6 @@ void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay } } - BOOST_LOG_TRIVIAL(error) << "render " << GLCanvas3D::timestamp_now() - m_last_render; m_last_render = GLCanvas3D::timestamp_now(); } @@ -1078,10 +1077,6 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas) ++it; } - BOOST_LOG_TRIVIAL(error) << "update " << request_render << " : " << next_render <<" : " << GLCanvas3D::timestamp_now() - m_last_update; - m_last_update = GLCanvas3D::timestamp_now(); - //BOOST_LOG_TRIVIAL(error) << time_since_render << ":" << next_render; - // render needed right now //if (next_render < 20) // request_render = true; diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 0d1cd9996..768f941a7 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -413,7 +413,6 @@ private: bool m_move_from_overlay { false }; // Timestamp of last rendering int64_t m_last_render { 0LL }; - int64_t m_last_update { 0LL }; //prepared (basic) notifications const std::vector basic_notifications = { From 6e325ee3221fe359ea951be3eae5f86adc3ff189 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 9 Feb 2021 09:19:59 +0100 Subject: [PATCH 29/60] cleanup --- src/slic3r/GUI/NotificationManager.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 28feb65d0..7e2a6b880 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -549,13 +549,14 @@ bool NotificationManager::PopNotification::compare_text(const std::string& text) bool NotificationManager::PopNotification::update_state(bool paused, const int64_t delta) { + + m_next_render = std::numeric_limits::max(); + if (m_state == EState::Unknown) { init(); return true; } - m_next_render = std::numeric_limits::max(); - if (m_state == EState::Hidden) { return false; } @@ -581,24 +582,24 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 int64_t curr_time = now - m_fading_start; int64_t next_render = FADING_OUT_TIMEOUT - delta; m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); - if (m_current_fade_opacity <= 0.0f) + if (m_current_fade_opacity <= 0.0f) { m_state = EState::Finished; - else if (next_render <= 20) { + return true; + } else if (next_render <= 20) { m_next_render = FADING_OUT_TIMEOUT; return true; - } - else + } else { m_next_render = next_render; + return false; + } } if (m_state == EState::Finished) { - //m_next_render = 0; return true; } if (m_state == EState::ClosePending) { m_state = EState::Finished; - //m_next_render = 0; return true; } return false; @@ -1077,10 +1078,7 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas) ++it; } - // render needed right now - //if (next_render < 20) - // request_render = true; - // request next frame + // request next frame in future if (next_render < max) canvas.schedule_extra_frame(int(next_render)); From bf032524ebe8f767836875978c9f9938342c976a Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 3 Mar 2021 09:24:16 +0100 Subject: [PATCH 30/60] notifications - minor changes in logic --- src/slic3r/GUI/NotificationManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 7e2a6b880..930174e3f 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -568,12 +568,12 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 m_current_fade_opacity = 1.0f; m_notification_start = now; // Timers when not fading - } else if (m_data.duration != 0 && !paused) { + } else if (m_state != EState::FadingOut && m_data.duration != 0 && !paused) { int64_t up_time = now - m_notification_start; - if (m_state != EState::FadingOut && up_time >= m_data.duration * 1000) { + if (up_time >= m_data.duration * 1000) { m_state = EState::FadingOut; m_fading_start = now; - } else if (m_state != EState::FadingOut) { + } else { m_next_render = m_data.duration * 1000 - up_time; } } From 6716492efa3c218da57a60c3937bb9777a20b25c Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 4 Mar 2021 21:56:07 +0100 Subject: [PATCH 31/60] Printhost upload progress bar notification --- src/slic3r/GUI/GUI_App.cpp | 5 ++ src/slic3r/GUI/GUI_App.hpp | 17 ++--- src/slic3r/GUI/NotificationManager.cpp | 87 +++++++++++++++++++++----- src/slic3r/GUI/NotificationManager.hpp | 37 +++++++++-- src/slic3r/GUI/PrintHostDialogs.cpp | 25 ++++++++ 5 files changed, 143 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 2fde30cd1..ecc5f46a7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1978,6 +1978,11 @@ wxNotebook* GUI_App::tab_panel() const return mainframe->m_tabpanel; } +NotificationManager* GUI_App::notification_manager() +{ + return plater_->get_notification_manager(); +} + // extruders count from selected printer preset int GUI_App::extruders_cnt() const { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 5572c5071..f1ee0746a 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -43,6 +43,7 @@ class ObjectSettings; class ObjectList; class ObjectLayers; class Plater; +class NotificationManager; struct GUI_InitParams; @@ -226,14 +227,14 @@ public: void MacOpenFiles(const wxArrayString &fileNames) override; #endif /* __APPLE */ - Sidebar& sidebar(); - ObjectManipulation* obj_manipul(); - ObjectSettings* obj_settings(); - ObjectList* obj_list(); - ObjectLayers* obj_layers(); - Plater* plater(); - Model& model(); - + Sidebar& sidebar(); + ObjectManipulation* obj_manipul(); + ObjectSettings* obj_settings(); + ObjectList* obj_list(); + ObjectLayers* obj_layers(); + Plater* plater(); + Model& model(); + NotificationManager* notification_manager(); // Parameters extracted from the command line to be passed to GUI after initialization. const GUI_InitParams* init_params { nullptr }; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 930174e3f..c9cf4d8cc 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -359,12 +359,14 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); // line2 - std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(line.c_str()); - cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; + if (m_text1.length() > m_endlines[0]) { + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(line.c_str()); + cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; + } } else { ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(cursor_y); @@ -776,17 +778,52 @@ void NotificationManager::ProgressBarNotification::init() } void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + // line1 - we do not print any more text than what fits on line 1. Line 2 is bar. + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 - win_size_y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); } void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); - float invisible_length = 0; - - ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length - m_window_width_offset, win_pos_y + win_size_y/2 + m_line_height / 2); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y/2 + m_line_height / 2); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); + switch (m_pb_state) + { + case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_PROGRESS: + { + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); + ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); + float invisible_length = 0; + ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length - m_window_width_offset, win_pos_y + win_size_y / 2 + m_line_height / 2); + ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + m_line_height / 2); + float full_lenght = lineEnd.x - lineStart.x; + ImVec2 midPoint = ImVec2(lineStart.x + full_lenght * m_percentage, lineStart.y); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); + ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); + break; + } + case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_ERROR: + { + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + imgui.text(_u8L("ERROR")); + break; + } + case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_CANCELLED: + { + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + imgui.text(_u8L("CANCELED")); + break; + } + case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_COMPLETED: + { + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + imgui.text(_u8L("COMPLETED")); + break; + } + } + } //------NotificationManager-------- @@ -989,7 +1026,6 @@ void NotificationManager::set_progress_bar_percentage(const std::string& text, f for (std::unique_ptr& notification : m_pop_notifications) { if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { dynamic_cast(notification.get())->set_percentage(percentage); - // FIX ME: this is massive gpu eater (render every frame) wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); found = true; } @@ -998,6 +1034,26 @@ void NotificationManager::set_progress_bar_percentage(const std::string& text, f push_progress_bar_notification(text, percentage); } } +void NotificationManager::progress_bar_show_canceled(const std::string& text) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { + dynamic_cast(notification.get())->cancel(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } +} +void NotificationManager::progress_bar_show_error(const std::string& text) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { + dynamic_cast(notification.get())->error(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } +} bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp) { return push_notification_data(std::make_unique(notification_data, m_id_provider, m_evt_handler), timestamp); @@ -1103,7 +1159,8 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi const std::string &new_text = notification->get_data().text1; for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { if ((*it)->get_type() == new_type && !(*it)->is_finished()) { - if (new_type == NotificationType::CustomNotification || new_type == NotificationType::PlaterWarning) { + if (std::find(m_multiple_types.begin(), m_multiple_types.end(), new_type) != m_multiple_types.end()) { + //if (new_type == NotificationType::CustomNotification || new_type == NotificationType::PlaterWarning || new_type == NotificationType::ProgressBar) { if (!(*it)->compare_text(new_text)) continue; } else if (new_type == NotificationType::SlicingWarning) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 768f941a7..2baaf7054 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -142,6 +142,8 @@ public: // notification with progress bar void push_progress_bar_notification(const std::string& text, float percentage = 0); void set_progress_bar_percentage(const std::string& text, float percentage); + void progress_bar_show_canceled(const std::string& text); + void progress_bar_show_error(const std::string& text); // Close old notification ExportFinished. void new_export_began(bool on_removable); // finds ExportFinished notification and closes it if it was to removable device @@ -228,7 +230,7 @@ private: bool is_hovered() const { return m_state == EState::Hovered; } // Call after every size change - void init(); + virtual void init(); // Part of init() virtual void count_spaces(); // Calculetes correct size but not se it in imgui! @@ -341,8 +343,28 @@ private: class ProgressBarNotification : public PopNotification { public: + enum class ProgressBarState + { + PB_PROGRESS, + PB_ERROR, + PB_CANCELLED, + PB_COMPLETED + }; ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); } - void set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) m_progress_complete = true; else m_progress_complete = false; } + void set_percentage(float percent) + { + if (m_pb_state == ProgressBarState::PB_CANCELLED) + return; + m_percentage = percent; + if (percent >= 1.0f) + m_pb_state = ProgressBarState::PB_COMPLETED; + else if (percent < 0.0f ) + m_pb_state = ProgressBarState::PB_ERROR; + else + m_pb_state = ProgressBarState::PB_PROGRESS; + } + void cancel() { m_pb_state = ProgressBarState::PB_CANCELLED; } + void error() { m_pb_state = ProgressBarState::PB_ERROR; } protected: virtual void init(); virtual void render_text(ImGuiWrapper& imgui, @@ -351,8 +373,12 @@ private: void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - bool m_progress_complete{ false }; - float m_percentage; + float m_percentage; + ProgressBarState m_pb_state { ProgressBarState::PB_PROGRESS }; + }; + + class PrintHostUploadNotification : public ProgressBarNotification + { }; class ExportFinishedNotification : public PopNotification @@ -413,7 +439,8 @@ private: bool m_move_from_overlay { false }; // Timestamp of last rendering int64_t m_last_render { 0LL }; - + // Notification types that can be shown multiple types at once (compared by text) + const std::vector m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar }; //prepared (basic) notifications const std::vector basic_notifications = { {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index a094b70e9..921337d4a 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -25,6 +25,7 @@ #include "wxExtensions.hpp" #include "MainFrame.hpp" #include "libslic3r/AppConfig.hpp" +#include "NotificationManager.hpp" namespace fs = boost::filesystem; @@ -280,6 +281,9 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) job_list->AppendItem(fields, static_cast(ST_NEW)); // Both strings are UTF-8 encoded. upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string()); + + std::string notification_text = "[" + std::to_string(job_list->GetItemCount()) + "] " + job.upload_data.upload_path.string() + " -> " + job.printhost->get_host(); + wxGetApp().notification_manager()->push_progress_bar_notification(notification_text); } void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect) @@ -345,6 +349,15 @@ void PrintHostQueueDialog::on_progress(Event &evt) } on_list_select(); + + if (evt.progress > 0) + { + wxVariant nm, hst; + job_list->GetValue(nm, evt.job_id, COL_FILENAME); + job_list->GetValue(hst, evt.job_id, COL_HOST); + std::string notification_text = "[" + std::to_string(evt.job_id + 1) + "] " + boost::nowide::narrow(nm.GetString()) + " -> " + boost::nowide::narrow(hst.GetString()); + wxGetApp().notification_manager()->set_progress_bar_percentage(notification_text, 100 / evt.progress); + } } void PrintHostQueueDialog::on_error(Event &evt) @@ -360,6 +373,12 @@ void PrintHostQueueDialog::on_error(Event &evt) on_list_select(); GUI::show_error(nullptr, errormsg); + + wxVariant nm, hst; + job_list->GetValue(nm, evt.job_id, COL_FILENAME); + job_list->GetValue(hst, evt.job_id, COL_HOST); + std::string notification_text = "[" + std::to_string(evt.job_id + 1) + "] " + boost::nowide::narrow(nm.GetString()) + " -> " + boost::nowide::narrow(hst.GetString()); + wxGetApp().notification_manager()->progress_bar_show_error(notification_text); } void PrintHostQueueDialog::on_cancel(Event &evt) @@ -370,6 +389,12 @@ void PrintHostQueueDialog::on_cancel(Event &evt) job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS); on_list_select(); + + wxVariant nm, hst; + job_list->GetValue(nm, evt.job_id, COL_FILENAME); + job_list->GetValue(hst, evt.job_id, COL_HOST); + std::string notification_text = "[" + std::to_string(evt.job_id + 1) + "] " + boost::nowide::narrow(nm.GetString()) + " -> " + boost::nowide::narrow(hst.GetString()); + wxGetApp().notification_manager()->progress_bar_show_canceled(notification_text); } void PrintHostQueueDialog::get_active_jobs(std::vector>& ret) From 44bfb914ab03d1e77ecbc50eeb7865dc76351150 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sat, 6 Mar 2021 11:26:29 +0100 Subject: [PATCH 32/60] progress bar notification - percentage text --- src/slic3r/GUI/NotificationManager.cpp | 39 +++++++++----------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index c9cf4d8cc..a5777cd55 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -786,44 +786,33 @@ void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& img } void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - switch (m_pb_state) - { + std::string text; + switch (m_pb_state) { case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_PROGRESS: { - ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); - ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); - float invisible_length = 0; - ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length - m_window_width_offset, win_pos_y + win_size_y / 2 + m_line_height / 2); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + m_line_height / 2); - float full_lenght = lineEnd.x - lineStart.x; - ImVec2 midPoint = ImVec2(lineStart.x + full_lenght * m_percentage, lineStart.y); + text = std::to_string((int)(m_percentage * 100)) + "%"; + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); + ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); + ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + m_line_height / 2); + ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation + ImGui::CalcTextSize(text.c_str()).x, win_pos_y + win_size_y / 2 + m_line_height / 2); + ImVec2 midPoint = ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); break; } case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_ERROR: - { - ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); - imgui.text(_u8L("ERROR")); + text = _u8L("ERROR"); break; - } case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_CANCELLED: - { - ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); - imgui.text(_u8L("CANCELED")); + text = _u8L("CANCELED"); break; - } case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_COMPLETED: - { - ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); - imgui.text(_u8L("COMPLETED")); + text = _u8L("COMPLETED"); break; } - } - + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + imgui.text(text.c_str()); } //------NotificationManager-------- From 62c2095fe8297688c254ef70d9742f18fffe1b77 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 11 Mar 2021 13:33:31 +0100 Subject: [PATCH 33/60] Print host upload notification with more info and cancel button --- resources/icons/notification_cancel.svg | 67 +++++++ resources/icons/notification_cancel_hover.svg | 67 +++++++ src/imgui/imconfig.h | 2 + src/slic3r/GUI/ImGuiWrapper.cpp | 4 +- src/slic3r/GUI/NotificationManager.cpp | 175 +++++++++++++++--- src/slic3r/GUI/NotificationManager.hpp | 92 +++++---- src/slic3r/GUI/PrintHostDialogs.cpp | 12 +- 7 files changed, 350 insertions(+), 69 deletions(-) create mode 100644 resources/icons/notification_cancel.svg create mode 100644 resources/icons/notification_cancel_hover.svg diff --git a/resources/icons/notification_cancel.svg b/resources/icons/notification_cancel.svg new file mode 100644 index 000000000..d849e24c6 --- /dev/null +++ b/resources/icons/notification_cancel.svg @@ -0,0 +1,67 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_cancel_hover.svg b/resources/icons/notification_cancel_hover.svg new file mode 100644 index 000000000..746d053e4 --- /dev/null +++ b/resources/icons/notification_cancel_hover.svg @@ -0,0 +1,67 @@ + +image/svg+xml + + + + + + + diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index d52294acd..1ee719288 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -123,6 +123,8 @@ namespace ImGui const char ErrorMarker = 0x11; const char EjectButton = 0x12; const char EjectHoverButton = 0x13; + const char CancelButton = 0x14; + const char CancelHoverButton = 0x15; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 1ed4b492f..db7af046b 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -51,7 +51,9 @@ static const std::map font_icons_large = { {ImGui::EjectButton , "notification_eject_sd" }, {ImGui::EjectHoverButton , "notification_eject_sd_hover" }, {ImGui::WarningMarker , "notification_warning" }, - {ImGui::ErrorMarker , "notification_error" } + {ImGui::ErrorMarker , "notification_error" }, + {ImGui::CancelButton , "notification_cancel" }, + {ImGui::CancelHoverButton , "notification_cancel_hover" }, }; const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index a5777cd55..21df0e86b 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -5,6 +5,7 @@ #include "Plater.hpp" #include "GLCanvas3D.hpp" #include "ImGuiWrapper.hpp" +#include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" @@ -776,44 +777,157 @@ void NotificationManager::ProgressBarNotification::init() m_lines_count++; m_endlines.push_back(m_endlines.back()); } +void NotificationManager::ProgressBarNotification::count_spaces() +{ + //determine line width + m_line_height = ImGui::CalcTextSize("A").y; + + m_left_indentation = m_line_height; + if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + std::string text; + text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + float picture_width = ImGui::CalcTextSize(text.c_str()).x; + m_left_indentation = picture_width + m_line_height / 2; + } + m_window_width_offset = m_line_height * (m_has_cancel_button ? 6 : 4); + m_window_width = m_line_height * 25; +} + void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { // line1 - we do not print any more text than what fits on line 1. Line 2 is bar. ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(win_size_y / 2 - win_size_y / 6 - m_line_height / 2); imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); + ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); + ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + m_line_height / 4); + ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + m_line_height / 4); + ImVec2 midPoint = ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); + ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); +} +//------PrintHostUploadNotification---------------- +void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) +{ + if (m_uj_state == UploadJobState::PB_CANCELLED) + return; + m_percentage = percent; + if (percent >= 1.0f) { + m_uj_state = UploadJobState::PB_COMPLETED; + m_has_cancel_button = false; + } else if (percent < 0.0f) { + error(); + } else { + m_uj_state = UploadJobState::PB_PROGRESS; + m_has_cancel_button = true; + } +} +void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { std::string text; - switch (m_pb_state) { - case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_PROGRESS: + switch (m_uj_state) { + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_PROGRESS: { - text = std::to_string((int)(m_percentage * 100)) + "%"; - ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); - ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); - ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + m_line_height / 2); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation + ImGui::CalcTextSize(text.c_str()).x, win_pos_y + win_size_y / 2 + m_line_height / 2); - ImVec2 midPoint = ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); - ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); + ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + float uploaded = m_file_size / 100 * m_percentage; + std::stringstream stream; + stream << std::fixed << std::setprecision(3) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; + text = stream.str(); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 /*- m_line_height / 4 * 3*/); break; } - case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_ERROR: + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_ERROR: text = _u8L("ERROR"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); break; - case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_CANCELLED: + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_CANCELLED: text = _u8L("CANCELED"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); break; - case Slic3r::GUI::NotificationManager::ProgressBarNotification::ProgressBarState::PB_COMPLETED: + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED: text = _u8L("COMPLETED"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); break; } - ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + imgui.text(text.c_str()); +} +void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y), + ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y), + true)) + { + button_text = ImGui::CancelHoverButton; + // tooltip + long time_now = wxGetLocalTime(); + if (m_hover_time > 0 && m_hover_time < time_now) { + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + } + if (m_hover_time == 0) + m_hover_time = time_now; + } + else + m_hover_time = 0; + + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) { + auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, m_job_id, m_job_id); + wxQueueEvent(m_evt_handler, evt); + } + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + { + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) { + auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, m_job_id, m_job_id); + wxQueueEvent(m_evt_handler, evt); + } + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } //------NotificationManager-------- NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : @@ -1004,40 +1118,47 @@ void NotificationManager::push_exporting_finished_notification(const std::string NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); } -void NotificationManager::push_progress_bar_notification(const std::string& text, float percentage) + +void NotificationManager::push_upload_job_notification(wxEvtHandler* evt_handler, int id, float filesize, const std::string& filename, const std::string& host, float percentage) { - NotificationData data{ NotificationType::ProgressBar, NotificationLevel::ProgressBarNotification, 0, text }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0), 0); + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); + NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 0, text }; + push_notification_data(std::make_unique(data, m_id_provider, evt_handler, 0, id, filesize), 0); } -void NotificationManager::set_progress_bar_percentage(const std::string& text, float percentage) +void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) { + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); bool found = false; for (std::unique_ptr& notification : m_pop_notifications) { if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { - dynamic_cast(notification.get())->set_percentage(percentage); + dynamic_cast(notification.get())->set_percentage(percentage); wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); found = true; } } + /* if (!found) { - push_progress_bar_notification(text, percentage); + push_upload_job_notification(id, filename, host, percentage); } + */ } -void NotificationManager::progress_bar_show_canceled(const std::string& text) +void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host) { + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); for (std::unique_ptr& notification : m_pop_notifications) { - if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { - dynamic_cast(notification.get())->cancel(); + if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { + dynamic_cast(notification.get())->cancel(); wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); break; } } } -void NotificationManager::progress_bar_show_error(const std::string& text) +void NotificationManager::upload_job_notification_show_error(int id, const std::string& filename, const std::string& host) { + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); for (std::unique_ptr& notification : m_pop_notifications) { - if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { - dynamic_cast(notification.get())->error(); + if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { + dynamic_cast(notification.get())->error(); wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); break; } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 2baaf7054..4b32a716f 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -66,6 +66,8 @@ enum class NotificationType PlaterWarning, // Progress bar instead of text. ProgressBar, + // Progress bar with info from Print Host Upload Queue dialog. + PrintHostUpload, // Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider. EmptyColorChangeCode, // Notification that custom supports/seams were deleted after mesh repair. @@ -140,10 +142,10 @@ public: // Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); // notification with progress bar - void push_progress_bar_notification(const std::string& text, float percentage = 0); - void set_progress_bar_percentage(const std::string& text, float percentage); - void progress_bar_show_canceled(const std::string& text); - void progress_bar_show_error(const std::string& text); + void push_upload_job_notification(wxEvtHandler* evt_handler, int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); + void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); + void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); + void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); // Close old notification ExportFinished. void new_export_began(bool on_removable); // finds ExportFinished notification and closes it if it was to removable device @@ -340,45 +342,69 @@ private: void show() { m_state = EState::Unknown; } }; + class ProgressBarNotification : public PopNotification { public: - enum class ProgressBarState - { - PB_PROGRESS, - PB_ERROR, - PB_CANCELLED, - PB_COMPLETED - }; + ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); } - void set_percentage(float percent) - { - if (m_pb_state == ProgressBarState::PB_CANCELLED) - return; - m_percentage = percent; - if (percent >= 1.0f) - m_pb_state = ProgressBarState::PB_COMPLETED; - else if (percent < 0.0f ) - m_pb_state = ProgressBarState::PB_ERROR; - else - m_pb_state = ProgressBarState::PB_PROGRESS; - } - void cancel() { m_pb_state = ProgressBarState::PB_CANCELLED; } - void error() { m_pb_state = ProgressBarState::PB_ERROR; } + virtual void set_percentage(float percent) { m_percentage = percent; } protected: - virtual void init(); + virtual void init() override; + virtual void count_spaces() override; virtual void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); - void render_bar(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + virtual void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + virtual void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) + {} float m_percentage; - ProgressBarState m_pb_state { ProgressBarState::PB_PROGRESS }; + + bool m_has_cancel_button {false}; + // local time of last hover for showing tooltip + }; + + class PrintHostUploadNotification : public ProgressBarNotification { + public: + enum class UploadJobState + { + PB_PROGRESS, + PB_ERROR, + PB_CANCELLED, + PB_COMPLETED + }; + PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize) + :ProgressBarNotification(n, id_provider, evt_handler, percentage) + , m_job_id(job_id) + , m_file_size(filesize) + { + m_has_cancel_button = true; + } + static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return "[" + std::to_string(id) + "] " + filename + " -> " + host; } + virtual void set_percentage(float percent); + void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } + void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; } + protected: + virtual void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + virtual void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + // Identifies job in cancel callback + int m_job_id; + // Size of uploaded size to be displayed in MB + float m_file_size; + long m_hover_time{ 0 }; + UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS }; }; class ExportFinishedNotification : public PopNotification @@ -440,7 +466,7 @@ private: // Timestamp of last rendering int64_t m_last_render { 0LL }; // Notification types that can be shown multiple types at once (compared by text) - const std::vector m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar }; + const std::vector m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload }; //prepared (basic) notifications const std::vector basic_notifications = { {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 921337d4a..f3a1fae98 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -282,8 +282,7 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) // Both strings are UTF-8 encoded. upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string()); - std::string notification_text = "[" + std::to_string(job_list->GetItemCount()) + "] " + job.upload_data.upload_path.string() + " -> " + job.printhost->get_host(); - wxGetApp().notification_manager()->push_progress_bar_notification(notification_text); + wxGetApp().notification_manager()->push_upload_job_notification(this, job_list->GetItemCount(), 2.64931f, job.upload_data.upload_path.string(), job.printhost->get_host()); } void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect) @@ -355,8 +354,7 @@ void PrintHostQueueDialog::on_progress(Event &evt) wxVariant nm, hst; job_list->GetValue(nm, evt.job_id, COL_FILENAME); job_list->GetValue(hst, evt.job_id, COL_HOST); - std::string notification_text = "[" + std::to_string(evt.job_id + 1) + "] " + boost::nowide::narrow(nm.GetString()) + " -> " + boost::nowide::narrow(hst.GetString()); - wxGetApp().notification_manager()->set_progress_bar_percentage(notification_text, 100 / evt.progress); + wxGetApp().notification_manager()->set_upload_job_notification_percentage(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()), 100 / evt.progress); } } @@ -377,8 +375,7 @@ void PrintHostQueueDialog::on_error(Event &evt) wxVariant nm, hst; job_list->GetValue(nm, evt.job_id, COL_FILENAME); job_list->GetValue(hst, evt.job_id, COL_HOST); - std::string notification_text = "[" + std::to_string(evt.job_id + 1) + "] " + boost::nowide::narrow(nm.GetString()) + " -> " + boost::nowide::narrow(hst.GetString()); - wxGetApp().notification_manager()->progress_bar_show_error(notification_text); + wxGetApp().notification_manager()->upload_job_notification_show_error(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString())); } void PrintHostQueueDialog::on_cancel(Event &evt) @@ -393,8 +390,7 @@ void PrintHostQueueDialog::on_cancel(Event &evt) wxVariant nm, hst; job_list->GetValue(nm, evt.job_id, COL_FILENAME); job_list->GetValue(hst, evt.job_id, COL_HOST); - std::string notification_text = "[" + std::to_string(evt.job_id + 1) + "] " + boost::nowide::narrow(nm.GetString()) + " -> " + boost::nowide::narrow(hst.GetString()); - wxGetApp().notification_manager()->progress_bar_show_canceled(notification_text); + wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString())); } void PrintHostQueueDialog::get_active_jobs(std::vector>& ret) From 15765eb99b65a82abc172bc065ca84dfa7423ed8 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 12 Mar 2021 10:19:13 +0100 Subject: [PATCH 34/60] Commented Print host upload notification until its tested --- src/slic3r/GUI/NotificationManager.cpp | 2 +- src/slic3r/GUI/PrintHostDialogs.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 21df0e86b..266814e09 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -839,7 +839,7 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); float uploaded = m_file_size / 100 * m_percentage; std::stringstream stream; - stream << std::fixed << std::setprecision(3) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; + stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; text = stream.str(); ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 /*- m_line_height / 4 * 3*/); diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index f3a1fae98..c8df141e9 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -282,7 +282,7 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) // Both strings are UTF-8 encoded. upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string()); - wxGetApp().notification_manager()->push_upload_job_notification(this, job_list->GetItemCount(), 2.64931f, job.upload_data.upload_path.string(), job.printhost->get_host()); + //wxGetApp().notification_manager()->push_upload_job_notification(this, job_list->GetItemCount(), 0, job.upload_data.upload_path.string(), job.printhost->get_host()); } void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect) From c18ad5f9d691593b691033fa9beec9aba7682e09 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 12 Mar 2021 11:30:06 +0100 Subject: [PATCH 35/60] Added a missing include (gcc) Removed several includes, hopefully they're not needed on any other platform. --- src/slic3r/GUI/PrintHostDialogs.cpp | 2 +- src/slic3r/GUI/PrintHostDialogs.hpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index c8df141e9..598b72b94 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -16,13 +16,13 @@ #include #include +#include #include "GUI.hpp" #include "GUI_App.hpp" #include "MsgDialog.hpp" #include "I18N.hpp" #include "../Utils/PrintHost.hpp" -#include "wxExtensions.hpp" #include "MainFrame.hpp" #include "libslic3r/AppConfig.hpp" #include "NotificationManager.hpp" diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index c185f57be..294593bd1 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -8,10 +8,8 @@ #include #include -#include "GUI.hpp" #include "GUI_Utils.hpp" #include "MsgDialog.hpp" -#include "../Utils/PrintHost.hpp" class wxButton; class wxTextCtrl; From 5f6253390fef6f3cfe0f19422723a811e081d5df Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 12 Mar 2021 16:26:34 +0100 Subject: [PATCH 36/60] Implemented suggestion of the auto color change, if model looks like sign --- src/slic3r/GUI/DoubleSlider.cpp | 2 ++ src/slic3r/GUI/GUI_Preview.cpp | 49 ++++++++++++++++++++++++++ src/slic3r/GUI/NotificationManager.cpp | 4 ++- src/slic3r/GUI/NotificationManager.hpp | 2 ++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 81945061b..00b9c2e29 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1930,6 +1930,8 @@ void Control::auto_color_change() double delta_area = scale_(scale_(25)); // equal to 25 mm2 for (auto object : print.objects()) { + if (object->layer_count() == 0) + continue; double prev_area = area(object->get_layer(0)->lslices); for (size_t i = 1; i < object->layers().size(); i++) { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2f11d66f5..da7d7810a 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1,4 +1,5 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/Layer.hpp" #include "GUI_Preview.hpp" #include "GUI_App.hpp" #include "GUI.hpp" @@ -24,6 +25,7 @@ // this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421 #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" +#include "NotificationManager.hpp" namespace Slic3r { namespace GUI { @@ -639,6 +641,53 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee else m_layers_slider->SetLayersTimes(m_gcode_result->time_statistics.modes.front().layers_times); + // Suggest the auto color change, if model looks like sign + if (ticks_info_from_model.gcodes.empty()) + { + NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); +// notif_mngr->close_notification_of_type(NotificationType::SignDetected); + + const Print& print = wxGetApp().plater()->fff_print(); + double delta_area = scale_(scale_(25)); // equal to 25 mm2 + + //bool is_possible_auto_color_change = false; + for (auto object : print.objects()) { + double height = object->height(); + coord_t longer_side = std::max(object->size().x(), object->size().y()); + if (height / longer_side > 0.3) + continue; + + const ExPolygons& bottom = object->get_layer(0)->lslices; + if (bottom.size() > 1 || !bottom[0].holes.empty()) + continue; + + double bottom_area = area(bottom); + int i; + for (i = 1; i < int(0.3 * object->layers().size()); i++) + if (area(object->get_layer(1)->lslices) != bottom_area) + break; + if (i < int(0.3 * object->layers().size())) + continue; + + double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); + if( bottom_area - top_area > delta_area) { + notif_mngr->push_notification( + NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, + _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", + _u8L("Apply auto color change to print"), + [this/*, notif_mngr*/](wxEvtHandler*) { + // notif_mngr->close_notification_of_type(NotificationType::SignDetected); + m_layers_slider->auto_color_change(); + return true; + }); + + notif_mngr->set_in_preview(true); + + break; + } + } + } + m_layers_slider_sizer->Show((size_t)0); Layout(); } diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 266814e09..eb028d3d3 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1298,7 +1298,9 @@ void NotificationManager::set_in_preview(bool preview) m_in_preview = preview; for (std::unique_ptr ¬ification : m_pop_notifications) { if (notification->get_type() == NotificationType::PlaterWarning) - notification->hide(preview); + notification->hide(preview); + if (notification->get_type() == NotificationType::SignDetected) + notification->hide(!preview); } } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 4b32a716f..222d6b155 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -74,6 +74,8 @@ enum class NotificationType CustomSupportsAndSeamRemovedAfterRepair, // Notification that auto adding of color changes is impossible EmptyAutoColorChange, + // Notification about detected sign + SignDetected, // Notification emitted by Print::validate PrintValidateWarning, // Notification telling user to quit SLA supports manual editing From 972dbe238f76aaf95dd232a6a655d9ea3a2d95b8 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 14 Mar 2021 18:01:10 +0100 Subject: [PATCH 37/60] preventing hidden notification to show when updated and close them correctly. --- src/slic3r/GUI/GLCanvas3D.cpp | 1 + src/slic3r/GUI/NotificationManager.cpp | 28 +++++++++++++++++--------- src/slic3r/GUI/NotificationManager.hpp | 27 ++++++++++++++++--------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0a2b5cd65..5352e21b3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1752,6 +1752,7 @@ void GLCanvas3D::render() m_tooltip.render(m_mouse.position, *this); wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); + wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); wxGetApp().imgui()->render(); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index eb028d3d3..a9ac68036 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1,12 +1,9 @@ #include "NotificationManager.hpp" -#include "GUI_App.hpp" + #include "GUI.hpp" -#include "Plater.hpp" -#include "GLCanvas3D.hpp" #include "ImGuiWrapper.hpp" #include "PrintHostDialogs.hpp" - #include "wxExtensions.hpp" #include @@ -147,6 +144,12 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init return; } + if (m_state == EState::ClosePending || m_state == EState::Finished) + { + m_state = EState::Finished; + return; + } + Size cnv_size = canvas.get_canvas_size(); ImGuiWrapper& imgui = *wxGetApp().imgui(); ImVec2 mouse_pos = ImGui::GetMousePos(); @@ -240,7 +243,11 @@ void NotificationManager::PopNotification::count_spaces() void NotificationManager::PopNotification::init() { - std::string text = m_text1 + " " + m_hypertext; + // Do not init closing notification + if (is_finished()) + return; + + std::string text = m_text1 + " " + m_hypertext; size_t last_end = 0; m_lines_count = 0; @@ -291,7 +298,9 @@ void NotificationManager::PopNotification::init() } if (m_lines_count == 3) m_multiline = true; - m_state = EState::Shown; + m_notification_start = GLCanvas3D::timestamp_now(); + //if (m_state != EState::Hidden) + // m_state = EState::Shown; } void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) { @@ -653,6 +662,7 @@ void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) // m_counting_down = !l; m_hypertext = l ? _u8L("Export G-Code.") : std::string(); m_state = l ? EState::Shown : EState::Hidden; + init(); } //---------------ExportFinishedNotification----------- void NotificationManager::ExportFinishedNotification::count_spaces() @@ -1270,9 +1280,10 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { if ((*it)->get_type() == new_type && !(*it)->is_finished()) { if (std::find(m_multiple_types.begin(), m_multiple_types.end(), new_type) != m_multiple_types.end()) { - //if (new_type == NotificationType::CustomNotification || new_type == NotificationType::PlaterWarning || new_type == NotificationType::ProgressBar) { - if (!(*it)->compare_text(new_text)) + // If found same type and same text, return true - update will be performed on the old notif + if ((*it)->compare_text(new_text) == false) { continue; + } } else if (new_type == NotificationType::SlicingWarning) { auto w1 = dynamic_cast(notification); auto w2 = dynamic_cast(it->get()); @@ -1284,7 +1295,6 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi continue; } } - if (it != m_pop_notifications.end() - 1) std::rotate(it, it + 1, m_pop_notifications.end()); return true; diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 222d6b155..e86ac8056 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -1,6 +1,9 @@ #ifndef slic3r_GUI_NotificationManager_hpp_ #define slic3r_GUI_NotificationManager_hpp_ +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "GLCanvas3D.hpp" #include "Event.hpp" #include "I18N.hpp" @@ -211,9 +214,9 @@ private: PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler); virtual ~PopNotification() { if (m_id) m_id_provider.release_id(m_id); } - void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); + virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); // close will dissapear notification on next render - virtual void close() { m_state = EState::ClosePending; } + virtual void close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);} // data from newer notification of same type void update(const NotificationData& n); bool is_finished() const { return m_state == EState::ClosePending || m_state == EState::Finished; } @@ -226,7 +229,7 @@ private: const bool is_gray() const { return m_is_gray; } void set_gray(bool g) { m_is_gray = g; } bool compare_text(const std::string& text); - void hide(bool h) { m_state = h ? EState::Hidden : EState::Unknown; } + void hide(bool h) { if (is_finished()) return; m_state = h ? EState::Hidden : EState::Unknown; } // sets m_next_render with time of next mandatory rendering. Delta is time since last render. bool update_state(bool paused, const int64_t delta); int64_t next_render() const { return is_finished() ? 0 : m_next_render; } @@ -313,10 +316,16 @@ private: { public: SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool largeds); - void set_large(bool l); - bool get_large() { return m_is_large; } - - void set_print_info(const std::string &info); + void set_large(bool l); + bool get_large() { return m_is_large; } + void set_print_info(const std::string &info); + virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) + { + // This notification is always hidden if !large (means side bar is collapsed) + if (!get_large() && !is_finished()) + m_state == EState::Hidden; + PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); + } protected: virtual void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, @@ -339,8 +348,8 @@ private: { public: PlaterWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {} - virtual void close() { m_state = EState::Hidden; } - void real_close() { m_state = EState::ClosePending; } + virtual void close() { if(is_finished()) return; m_state = EState::Hidden; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } + void real_close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } void show() { m_state = EState::Unknown; } }; From ffb13767f44d0d4aaf71f8edbc9d5863c15f86fa Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 14 Mar 2021 18:35:36 +0100 Subject: [PATCH 38/60] typo correction --- src/slic3r/GUI/NotificationManager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index e86ac8056..651deace8 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -323,7 +323,7 @@ private: { // This notification is always hidden if !large (means side bar is collapsed) if (!get_large() && !is_finished()) - m_state == EState::Hidden; + m_state = EState::Hidden; PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); } protected: From 1569dad5de053920328fc9888afdadcfb62df0aa Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 15 Mar 2021 09:54:24 +0100 Subject: [PATCH 39/60] Auto color change: Fixed show/hide for the notification. --- src/slic3r/GUI/GUI_Preview.cpp | 6 +++--- src/slic3r/GUI/Plater.cpp | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index da7d7810a..9f9f20ffb 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -645,7 +645,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (ticks_info_from_model.gcodes.empty()) { NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); -// notif_mngr->close_notification_of_type(NotificationType::SignDetected); + notif_mngr->close_notification_of_type(NotificationType::SignDetected); const Print& print = wxGetApp().plater()->fff_print(); double delta_area = scale_(scale_(25)); // equal to 25 mm2 @@ -675,8 +675,8 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", _u8L("Apply auto color change to print"), - [this/*, notif_mngr*/](wxEvtHandler*) { - // notif_mngr->close_notification_of_type(NotificationType::SignDetected); + [this, notif_mngr](wxEvtHandler*) { + notif_mngr->close_notification_of_type(NotificationType::SignDetected); m_layers_slider->auto_color_change(); return true; }); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3d4d381fb..c6b3ed89f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3563,6 +3563,7 @@ void Plater::priv::on_slicing_began() { clear_warnings(); notification_manager->close_notification_of_type(NotificationType::SlicingComplete); + notification_manager->close_notification_of_type(NotificationType::SignDetected); } void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) { From ceea9de8b8b1d4b35bdd1180051033680c405ba1 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 8 Mar 2021 13:44:00 +0100 Subject: [PATCH 40/60] WIP: Refactored bridging flow from normal flow, new config value 'thick_bridges' to switch between the Slic3r vs. S3D/Cura/Ideamaker way of printing 1st object layer over supports. Simplified the PresetHints. --- src/libslic3r/Brim.cpp | 8 +- src/libslic3r/ElephantFootCompensation.cpp | 2 +- src/libslic3r/ExtrusionEntity.cpp | 4 +- src/libslic3r/Fill/Fill.cpp | 67 ++++++---------- src/libslic3r/Flow.cpp | 68 +++++++--------- src/libslic3r/Flow.hpp | 40 ++++++---- src/libslic3r/GCode.cpp | 19 +++-- src/libslic3r/Layer.hpp | 4 +- src/libslic3r/LayerRegion.cpp | 24 +++--- src/libslic3r/PerimeterGenerator.cpp | 28 +++---- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 10 +-- src/libslic3r/Print.hpp | 3 +- src/libslic3r/PrintConfig.cpp | 7 ++ src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/PrintObject.cpp | 12 +-- src/libslic3r/PrintRegion.cpp | 53 ++++++------ src/libslic3r/SupportMaterial.cpp | 62 +++++++-------- src/slic3r/GUI/PresetHints.cpp | 93 ++++++---------------- src/slic3r/GUI/Tab.cpp | 1 + tests/fff_print/test_flow.cpp | 2 +- 21 files changed, 228 insertions(+), 283 deletions(-) diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index d5ec0d928..08bedc5c0 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -320,7 +320,7 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_ loops = union_pt_chained_outside_in(loops, false); std::reverse(loops.begin(), loops.end()); extrusion_entities_append_loops(brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), - float(flow.width), float(print.skirt_first_layer_height())); + float(flow.width()), float(print.skirt_first_layer_height())); } // Produce brim lines around those objects, that have the brim enabled. @@ -495,7 +495,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) { auto *loop = new ExtrusionLoop(); brim.entities.emplace_back(loop); - loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())); + loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); Points &points = loop->paths.front().polyline.points; points.reserve(first_path.size()); for (const ClipperLib_Z::IntPoint &pt : first_path) @@ -506,7 +506,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance ExtrusionEntityCollection this_loop_trimmed; this_loop_trimmed.entities.reserve(j - i); for (; i < j; ++ i) { - this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()))); + this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()))); const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; Points &points = static_cast(this_loop_trimmed.entities.back())->polyline.points; points.reserve(path.size()); @@ -522,7 +522,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance } } } else { - extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())); + extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); } make_inner_brim(print, top_level_objects_with_brim, brim); diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp index f28d88f7e..0895e16d6 100644 --- a/src/libslic3r/ElephantFootCompensation.cpp +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -621,7 +621,7 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_c ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation) { // The contour shall be wide enough to apply the external perimeter plus compensation on both sides. - double min_contour_width = double(external_perimeter_flow.width + external_perimeter_flow.spacing()); + double min_contour_width = double(external_perimeter_flow.width() + external_perimeter_flow.spacing()); return elephant_foot_compensation(input, min_contour_width, compensation); } diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 390d107f2..3284bc39e 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -52,7 +52,9 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale { // Instantiating the Flow class to get the line spacing. // Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler. - Flow flow(this->width, this->height, 0.f, is_bridge(this->role())); + bool bridge = is_bridge(this->role()); + assert(! bridge || this->width == this->height); + auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f); polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); } diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index ee493ca9c..129a9440c 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -42,7 +42,7 @@ struct SurfaceFillParams // width, height of extrusion, nozzle diameter, is bridge // For the output, for fill generator. - Flow flow = Flow(0.f, 0.f, 0.f, false); + Flow flow; // For the output ExtrusionRole extrusion_role = ExtrusionRole(0); @@ -70,10 +70,10 @@ struct SurfaceFillParams // RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust); RETURN_COMPARE_NON_EQUAL(anchor_length); RETURN_COMPARE_NON_EQUAL(anchor_length_max); - RETURN_COMPARE_NON_EQUAL(flow.width); - RETURN_COMPARE_NON_EQUAL(flow.height); - RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter); - RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge); + RETURN_COMPARE_NON_EQUAL(flow.width()); + RETURN_COMPARE_NON_EQUAL(flow.height()); + RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter()); + RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge()); RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role); return false; } @@ -143,17 +143,12 @@ std::vector group_fills(const Layer &layer) params.bridge_angle = float(surface.bridge_angle); params.angle = float(Geometry::deg2rad(region_config.fill_angle.value)); - // calculate the actual flow we'll be using for this infill - params.flow = layerm.region()->flow( - extrusion_role, - (surface.thickness == -1) ? layer.height : surface.thickness, // extrusion height - is_bridge || Fill::use_bridge_flow(params.pattern), // bridge flow? - layer.id() == 0, // first layer? - -1, // auto width - *layer.object() - ); - - // Calculate flow spacing for infill pattern generation. + // Calculate the actual flow we'll be using for this infill. + params.flow = is_bridge || Fill::use_bridge_flow(params.pattern) ? + layerm.bridging_flow(extrusion_role) : + layerm.region()->flow(*layer.object(), extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness, layer.id() == 0); + + // Calculate flow spacing for infill pattern generation. if (surface.is_solid() || is_bridge) { params.spacing = params.flow.spacing(); // Don't limit anchor length for solid or bridging infill. @@ -164,14 +159,7 @@ std::vector group_fills(const Layer &layer) // for all layers, for avoiding the ugly effect of // misaligned infill on first layer because of different extrusion width and // layer height - params.spacing = layerm.region()->flow( - frInfill, - layer.object()->config().layer_height.value, // TODO: handle infill_every_layers? - false, // no bridge - false, // no first layer - -1, // auto width - *layer.object() - ).spacing(); + params.spacing = layerm.region()->flow(*layer.object(), frInfill, layer.object()->config().layer_height).spacing(); // Anchor a sparse infill to inner perimeters with the following anchor length: params.anchor_length = float(region_config.infill_anchor); if (region_config.infill_anchor.percent) @@ -278,7 +266,7 @@ std::vector group_fills(const Layer &layer) region_id = region_some_infill; const LayerRegion& layerm = *layer.regions()[region_id]; for (SurfaceFill &surface_fill : surface_fills) - if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height) < EPSILON) { + if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) { internal_solid_fill = &surface_fill; break; } @@ -290,14 +278,7 @@ std::vector group_fills(const Layer &layer) params.extrusion_role = erInternalInfill; params.angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); // calculate the actual flow we'll be using for this infill - params.flow = layerm.region()->flow( - frSolidInfill, - layer.height, // extrusion height - false, // bridge flow? - layer.id() == 0, // first layer? - -1, // auto width - *layer.object() - ); + params.flow = layerm.region()->flow(*layer.object(), frSolidInfill, layer.height, layer.id() == 0); params.spacing = params.flow.spacing(); surface_fills.emplace_back(params); surface_fills.back().surface.surface_type = stInternalSolid; @@ -365,9 +346,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; // calculate flow spacing for infill pattern generation - bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; + bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge(); double link_max_length = 0.; - if (! surface_fill.params.flow.bridge) { + if (! surface_fill.params.flow.bridge()) { #if 0 link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); // printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length); @@ -380,7 +361,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // Maximum length of the perimeter segment linking two infill lines. f->link_max_length = (coord_t)scale_(link_max_length); // Used by the concentric infill pattern to clip the loops to create extrusion paths. - f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); // apply half spacing using this flow's own spacing and generate infill FillParams params; @@ -402,15 +383,17 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // calculate actual flow from spacing (which might have been adjusted by the infill // pattern generator) double flow_mm3_per_mm = surface_fill.params.flow.mm3_per_mm(); - double flow_width = surface_fill.params.flow.width; + double flow_width = surface_fill.params.flow.width(); if (using_internal_flow) { // if we used the internal flow we're not doing a solid infill // so we can safely ignore the slight variation that might have // been applied to f->spacing } else { - Flow new_flow = Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter, surface_fill.params.flow.height, surface_fill.params.flow.bridge); + Flow new_flow = surface_fill.params.flow.bridge() ? + Flow::bridging_flow_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter()) : + Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter(), surface_fill.params.flow.height()); flow_mm3_per_mm = new_flow.mm3_per_mm(); - flow_width = new_flow.width; + flow_width = new_flow.width(); } // Save into layer. ExtrusionEntityCollection* eec = nullptr; @@ -420,7 +403,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: extrusion_entities_append_paths( eec->entities, std::move(polylines), surface_fill.params.extrusion_role, - flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height); + flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); } } } @@ -619,7 +602,7 @@ void Layer::make_ironing() fill.angle = float(ironing_params.angle + 0.25 * M_PI); fill.link_max_length = (coord_t)scale_(3. * fill.spacing); double height = ironing_params.height * fill.spacing / nozzle_dmr; - Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), false); + Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height)); double flow_mm3_per_mm = flow.mm3_per_mm(); Surface surface_fill(stTop, ExPolygon()); for (ExPolygon &expoly : ironing_areas) { @@ -638,7 +621,7 @@ void Layer::make_ironing() extrusion_entities_append_paths( eec->entities, std::move(polylines), erIroning, - flow_mm3_per_mm, float(flow.width), float(height)); + flow_mm3_per_mm, float(flow.width()), float(height)); } } } diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index e5dcf0731..103123cdb 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -6,6 +6,12 @@ #include +// Overlap factor of perimeter lines. Currently no overlap. +// #define HAS_PERIMETER_LINE_OVERLAP +#ifdef HAS_PERIMETER_LINE_OVERLAP + #define PERIMETER_LINE_OVERLAP_FACTOR 1.0 +#endif + // Mark string for localization and translate. #define L(s) Slic3r::I18N::translate(s) @@ -122,20 +128,13 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol // This constructor builds a Flow object from an extrusion width config setting // and other context properties. -Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio) +Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height) { - // we need layer height unless it's a bridge - if (height <= 0 && bridge_flow_ratio == 0) + if (height <= 0) throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()"); float w; - if (bridge_flow_ratio > 0) { - // If bridge flow was requested, calculate the bridge width. - height = w = (bridge_flow_ratio == 1.) ? - // optimization to avoid sqrt() - nozzle_diameter : - sqrt(bridge_flow_ratio) * nozzle_diameter; - } else if (! width.percent && width.value == 0.) { + if (! width.percent && width.value == 0.) { // If user left option to 0, calculate a sane default width. w = auto_extrusion_width(role, nozzle_diameter); } else { @@ -143,26 +142,23 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent w = float(width.get_abs_value(height)); } - return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0); + return Flow(w, height, nozzle_diameter, false); } // This constructor builds a Flow object from a given centerline spacing. -Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) +Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height) { - // we need layer height unless it's a bridge - if (height <= 0 && !bridge) + if (height <= 0) throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()"); // Calculate width from spacing. // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions. - // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads. - float width = float(bridge ? - (spacing - BRIDGE_EXTRA_SPACING) : + float width = float( #ifdef HAS_PERIMETER_LINE_OVERLAP (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI)); #else (spacing + height * (1. - 0.25 * PI))); #endif - return Flow(width, bridge ? width : height, nozzle_diameter, bridge); + return Flow(width, height, nozzle_diameter); } // This method returns the centerline spacing between two adjacent extrusions @@ -170,13 +166,13 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, float Flow::spacing() const { #ifdef HAS_PERIMETER_LINE_OVERLAP - if (this->bridge) - return this->width + BRIDGE_EXTRA_SPACING; + if (m_bridge) + return m_width + BRIDGE_EXTRA_SPACING; // rectangle with semicircles at the ends - float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI); - float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); + float min_flow_spacing = m_width - m_height * (1. - 0.25 * PI); + float res = m_width - PERIMETER_LINE_OVERLAP_FACTOR * (m_width - min_flow_spacing); #else - float res = float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI))); + float res = float(m_bridge ? (m_width + BRIDGE_EXTRA_SPACING) : (m_width - m_height * (1. - 0.25 * PI))); #endif // assert(res > 0.f); if (res <= 0.f) @@ -189,10 +185,10 @@ float Flow::spacing() const // this->spacing(other) shall return the same value as other.spacing(*this) float Flow::spacing(const Flow &other) const { - assert(this->height == other.height); - assert(this->bridge == other.bridge); - float res = float(this->bridge ? - 0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING : + assert(m_height == other.m_height); + assert(m_bridge == other.m_bridge); + float res = float(m_bridge ? + 0.5 * m_width + 0.5 * other.m_width + BRIDGE_EXTRA_SPACING : 0.5 * this->spacing() + 0.5 * other.spacing()); // assert(res > 0.f); if (res <= 0.f) @@ -203,11 +199,11 @@ float Flow::spacing(const Flow &other) const // This method returns extrusion volume per head move unit. double Flow::mm3_per_mm() const { - float res = this->bridge ? + float res = m_bridge ? // Area of a circle with dmr of this->width. - float((this->width * this->width) * 0.25 * PI) : + float((m_width * m_width) * 0.25 * PI) : // Rectangle with semicircles at the ends. ~ h (w - 0.215 h) - float(this->height * (this->width - this->height * (1. - 0.25 * PI))); + float(m_height * (m_width - m_height * (1. - 0.25 * PI))); //assert(res > 0.); if (res <= 0.) throw FlowErrorNegativeFlow(); @@ -222,9 +218,7 @@ Flow support_material_flow(const PrintObject *object, float layer_height) (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width, // if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), - (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value), - // bridge_flow_ratio - 0.f); + (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) @@ -235,9 +229,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (width.value > 0) ? width : object->config().extrusion_width, float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), - (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)), - // bridge_flow_ratio - 0.f); + (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value))); } Flow support_material_interface_flow(const PrintObject *object, float layer_height) @@ -248,9 +240,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig (object->config().support_material_extrusion_width > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width, // if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)), - (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value), - // bridge_flow_ratio - 0.f); + (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } } diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 9e57ce907..101dc6880 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -13,11 +13,6 @@ class PrintObject; // Extra spacing of bridge threads, in mm. #define BRIDGE_EXTRA_SPACING 0.05 -// Overlap factor of perimeter lines. Currently no overlap. -#ifdef HAS_PERIMETER_LINE_OVERLAP - #define PERIMETER_LINE_OVERLAP_FACTOR 1.0 -#endif - enum FlowRole { frExternalPerimeter, frPerimeter, @@ -58,22 +53,22 @@ class Flow public: // Non bridging flow: Maximum width of an extrusion with semicircles at the ends. // Bridging flow: Bridge thread diameter. - float width; + float width() const { return m_width; } // Non bridging flow: Layer height. // Bridging flow: Bridge thread diameter = layer height. - float height; + float height() const { return m_height; } // Nozzle diameter. - float nozzle_diameter; + float nozzle_diameter() const { return m_nozzle_diameter; } // Is it a bridge? - bool bridge; + bool bridge() const { return m_bridge; } - Flow(float _w, float _h, float _nd, bool _bridge = false) : - width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {} + Flow() = default; + Flow(float w, float h, float nozzle_diameter) : Flow(w, h, nozzle_diameter, false) {} float spacing() const; float spacing(const Flow &other) const; double mm3_per_mm() const; - coord_t scaled_width() const { return coord_t(scale_(this->width)); } + coord_t scaled_width() const { return coord_t(scale_(m_width)); } coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); } coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); } @@ -83,13 +78,20 @@ public: // Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation. coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); } - bool operator==(const Flow &rhs) const { return this->width == rhs.width && this->height == rhs.height && this->nozzle_diameter == rhs.nozzle_diameter && this->bridge == rhs.bridge; } + bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; } + + Flow with_width (float width) const { assert(! m_bridge); return Flow(width, m_height, m_nozzle_diameter, m_bridge); } + Flow with_height(float height) const { assert(! m_bridge); return Flow(m_width, height, m_nozzle_diameter, m_bridge); } + + static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, nozzle_diameter, true }; } + static Flow bridging_flow_from_spacing(float spacing, float nozzle_diameter) + { auto dmr = spacing - float(BRIDGE_EXTRA_SPACING); return Flow { dmr, dmr, nozzle_diameter, true }; } - static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); + static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height); // Create a flow from the spacing of extrusion lines. // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale // to fit a region with integer number of lines. - static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); + static Flow new_from_spacing(float spacing, float nozzle_diameter, float height); // Sane extrusion width defautl based on nozzle diameter. // The defaults were derived from manual Prusa MK3 profiles. @@ -100,6 +102,14 @@ public: // on active extruder etc. Therefore the value calculated by this function shall be used as a hint only. static double extrusion_width(const std::string &opt_key, const ConfigOptionFloatOrPercent *opt, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0); static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0); + +private: + Flow(float w, float h, float nozzle_diameter, bool bridge) : m_width(w), m_height(h), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge) {} + + float m_width { 0 }; + float m_height { 0 }; + float m_nozzle_diameter { 0 }; + bool m_bridge { false }; }; extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 512035798..fd6c82b84 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1105,15 +1105,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu const double layer_height = first_object->config().layer_height.value; const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height); for (const PrintRegion* region : print.regions()) { - _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width); - _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width); - _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width); - _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(frSolidInfill, layer_height, false, false, -1., *first_object).width); - _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width); + _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width()); + _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, layer_height).width()); + _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(*first_object, frInfill, layer_height).width()); + _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(*first_object, frSolidInfill, layer_height).width()); + _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(*first_object, frTopSolidInfill, layer_height).width()); if (print.has_support_material()) - _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width); + _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); if (print.config().first_layer_extrusion_width.value > 0) - _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width); + _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, first_layer_height, true).width()); _write_format(file, "\n"); } print.throw_if_canceled(); @@ -2170,14 +2170,13 @@ void GCode::process_layer( const std::pair loops = loops_it->second; this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); - Flow layer_skirt_flow(print.skirt_flow()); - layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])); + Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); for (size_t i = loops.first; i < loops.second; ++i) { // Adjust flow according to this layer's layer height. ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]); for (ExtrusionPath &path : loop.paths) { - path.height = layer_skirt_flow.height; + path.height = layer_skirt_flow.height(); path.mm3_per_mm = mm3_per_mm; } //FIXME using the support_material_speed of the 1st object printed. diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 9a3fe368d..ef3124dcb 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -59,7 +59,9 @@ public: // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection fills; - Flow flow(FlowRole role, bool bridge = false, double width = -1) const; + Flow flow(FlowRole role) const; + Flow bridging_flow(FlowRole role) const; + void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 1a0bd341c..4b8fcaef9 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -15,16 +15,14 @@ namespace Slic3r { -Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const +Flow LayerRegion::flow(FlowRole role) const { - return m_region->flow( - role, - m_layer->height, - bridge, - m_layer->id() == 0, - width, - *m_layer->object() - ); + return m_region->flow(*m_layer->object(), role, m_layer->height, m_layer->id() == 0); +} + +Flow LayerRegion::bridging_flow(FlowRole role) const +{ + return this->layer()->object()->config().thick_bridges ? m_region->bridging_flow(role) : this->flow(role); } // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. @@ -84,7 +82,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec g.layer_id = (int)this->layer()->id(); g.ext_perimeter_flow = this->flow(frExternalPerimeter); - g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object()); + g.overhang_flow = this->bridging_flow(frPerimeter); g.solid_infill_flow = this->flow(frSolidInfill); g.process(); @@ -266,11 +264,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly // would get merged into a single one while they need different directions // also, supply the original expolygon instead of the grown one, because in case // of very thin (but still working) anchors, the grown expolygon would go beyond them - BridgeDetector bd( - initial, - lower_layer->lslices, - this->flow(frInfill, true).scaled_width() - ); + BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill).scaled_width()); #ifdef SLIC3R_DEBUG printf("Processing bridge at layer %zu:\n", this->layer()->id()); #endif diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 7cfdc5847..d33e1cfd4 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -8,7 +8,7 @@ namespace Slic3r { -static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance) +static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance) { ExtrusionPaths paths; ExtrusionPath path(role); @@ -62,15 +62,15 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi path.polyline.append(line.b); // Convert from spacing to extrusion width based on the extrusion model // of a square extrusion ended with semi circles. - flow.width = unscale(w) + flow.height * float(1. - 0.25 * PI); + Flow new_flow = flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI)); #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); #endif - path.mm3_per_mm = flow.mm3_per_mm(); - path.width = flow.width; - path.height = flow.height; + path.mm3_per_mm = new_flow.mm3_per_mm(); + path.width = new_flow.width(); + path.height = new_flow.height(); } else { - thickness_delta = fabs(scale_(flow.width) - w); + thickness_delta = fabs(scale_(flow.width()) - w); if (thickness_delta <= tolerance) { // the width difference between this line and the current flow width is // within the accepted tolerance @@ -88,7 +88,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi return paths; } -static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector &out) +static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow &flow, std::vector &out) { // This value determines granularity of adaptive width, as G-code does not allow // variable extrusion within a single move; this value shall only affect the amount @@ -205,8 +205,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime paths, intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()), role, - is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(), - is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width, + is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(), + is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(), (float)perimeter_generator.layer_height); // get overhang paths by checking what parts of this loop fall @@ -217,8 +217,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()), erOverhangPerimeter, perimeter_generator.mm3_per_mm_overhang(), - perimeter_generator.overhang_flow.width, - perimeter_generator.overhang_flow.height); + perimeter_generator.overhang_flow.width(), + perimeter_generator.overhang_flow.height()); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. @@ -226,8 +226,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { ExtrusionPath path(role); path.polyline = polygon.split_at_first_point(); - path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(); - path.width = is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width; + path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(); + path.width = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(); path.height = (float)perimeter_generator.layer_height; paths.push_back(path); } @@ -346,7 +346,7 @@ void PerimeterGenerator::process() if (this->config->thin_walls) { // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) - coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3)); + coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); ExPolygons expp = offset2_ex( // medial axis requires non-overlapping geometry diff_ex(to_polygons(last), diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 67b1ebd4f..81a14eaec 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -430,7 +430,7 @@ const std::vector& Preset::print_options() "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", - "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius", + "support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c5babb248..79c4e72a7 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1577,9 +1577,7 @@ Flow Print::brim_flow() const frPerimeter, width, (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), - (float)this->skirt_first_layer_height(), - 0 - ); + (float)this->skirt_first_layer_height()); } Flow Print::skirt_flow() const @@ -1599,9 +1597,7 @@ Flow Print::skirt_flow() const frPerimeter, width, (float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), - (float)this->skirt_first_layer_height(), - 0 - ); + (float)this->skirt_first_layer_height()); } bool Print::has_support_material() const @@ -1818,7 +1814,7 @@ void Print::_make_skirt() ExtrusionPath( erSkirt, (float)mm3_per_mm, // this will be overridden at G-code export time - flow.width, + flow.width(), (float)first_layer_height // this will be overridden at G-code export time ))); eloop.paths.back().polyline = loop.split_at_first_point(); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7311bdc79..04cf58fa9 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -65,7 +65,8 @@ public: const PrintRegionConfig& config() const { return m_config; } // 1-based extruder identifier for this region and role. unsigned int extruder(FlowRole role) const; - Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; + Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const; + Flow bridging_flow(FlowRole role) const; // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; // Average diameter of nozzles participating on extruding this region. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e534f140a..b87042b89 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2415,6 +2415,13 @@ void PrintConfigDef::init_fff_params() def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); + def = this->add("thick_bridges", coBool); + def->label = L("Thick bridges"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Print bridges with round extrusions."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("thin_walls", coBool); def->label = L("Detect thin walls"); def->category = L("Layers and Perimeters"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 76085c941..c92abf51f 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -522,6 +522,7 @@ public: ConfigOptionInt support_material_threshold; ConfigOptionBool support_material_with_sheath; ConfigOptionFloatOrPercent support_material_xy_spacing; + ConfigOptionBool thick_bridges; ConfigOptionFloat xy_size_compensation; ConfigOptionBool wipe_into_objects; @@ -569,6 +570,7 @@ protected: OPT_PTR(support_material_xy_spacing); OPT_PTR(support_material_threshold); OPT_PTR(support_material_with_sheath); + OPT_PTR(thick_bridges); OPT_PTR(xy_size_compensation); OPT_PTR(wipe_into_objects); } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 52bdc87e7..593955576 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -554,6 +554,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" || opt_key == "thin_walls" + || opt_key == "thick_bridges" || opt_key == "external_perimeters_first") { steps.emplace_back(posPerimeters); } else if ( @@ -1459,14 +1460,7 @@ void PrintObject::bridge_over_infill() if (region.config().fill_density.value == 100) continue; // get bridge flow - Flow bridge_flow = region.flow( - frSolidInfill, - -1, // layer height, not relevant for bridge flow - true, // bridge - false, // first layer - -1, // custom width, not relevant for bridge flow - *this - ); + Flow bridge_flow = region.bridging_flow(frSolidInfill); for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) { // skip first layer @@ -1488,7 +1482,7 @@ void PrintObject::bridge_over_infill() Polygons to_bridge_pp = internal_solid; // iterate through lower layers spanned by bridge_flow - double bottom_z = layer->print_z - bridge_flow.height; + double bottom_z = layer->print_z - bridge_flow.height(); for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { const Layer* lower_layer = m_layers[i]; diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 79eb647f6..acb429821 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -18,31 +18,25 @@ unsigned int PrintRegion::extruder(FlowRole role) const return extruder; } -Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const +Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const { ConfigOptionFloatOrPercent config_width; - if (width != -1) { - // use the supplied custom width, if any - config_width.value = width; - config_width.percent = false; + // Get extrusion width from configuration. + // (might be an absolute value, or a percent value, or zero for auto) + if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) { + config_width = m_print->config().first_layer_extrusion_width; + } else if (role == frExternalPerimeter) { + config_width = m_config.external_perimeter_extrusion_width; + } else if (role == frPerimeter) { + config_width = m_config.perimeter_extrusion_width; + } else if (role == frInfill) { + config_width = m_config.infill_extrusion_width; + } else if (role == frSolidInfill) { + config_width = m_config.solid_infill_extrusion_width; + } else if (role == frTopSolidInfill) { + config_width = m_config.top_infill_extrusion_width; } else { - // otherwise, get extrusion width from configuration - // (might be an absolute value, or a percent value, or zero for auto) - if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) { - config_width = m_print->config().first_layer_extrusion_width; - } else if (role == frExternalPerimeter) { - config_width = m_config.external_perimeter_extrusion_width; - } else if (role == frPerimeter) { - config_width = m_config.perimeter_extrusion_width; - } else if (role == frInfill) { - config_width = m_config.infill_extrusion_width; - } else if (role == frSolidInfill) { - config_width = m_config.solid_infill_extrusion_width; - } else if (role == frTopSolidInfill) { - config_width = m_config.top_infill_extrusion_width; - } else { - throw Slic3r::InvalidArgument("Unknown role"); - } + throw Slic3r::InvalidArgument("Unknown role"); } if (config_width.value == 0) @@ -50,8 +44,19 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1); - return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0f); + auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1)); + return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); +} + +Flow PrintRegion::bridging_flow(FlowRole role) const +{ + // Get the configured nozzle_diameter for the extruder associated to the flow role requested. + // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. + auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1)); + double bfr = m_config.bridge_flow_ratio; + if (bfr != 1.) + bfr = sqrt(bfr); + return Flow::bridging_flow(float(bfr) * nozzle_diameter, nozzle_diameter); } coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 459a15603..7c018c974 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -345,7 +345,7 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) if (! object->region_volumes[region_id].empty()) external_perimeter_width = std::max(external_perimeter_width, - (coordf_t)object->print()->get_region(region_id)->flow(frExternalPerimeter, slicing_params.layer_height, false, false, -1, *object).width); + (coordf_t)object->print()->get_region(region_id)->flow(*object, frExternalPerimeter, slicing_params.layer_height).width()); m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; @@ -1231,7 +1231,7 @@ namespace SupportMaterialInternal { // since we're dealing with bridges, we can't assume width is larger than spacing, // so we take the largest value and also apply safety offset to be ensure no gaps // are left in between - Flow bridge_flow = layerm->flow(frPerimeter, true); + Flow bridge_flow = layerm->bridging_flow(frPerimeter); float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); for (Polyline &polyline : overhang_perimeters) if (polyline.is_straight()) { @@ -1542,16 +1542,20 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } } // Offset the contact polygons outside. +#if 0 for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { diff_polygons = diff( offset( diff_polygons, - SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS, + scaled(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS), ClipperLib::jtRound, // round mitter limit scale_(0.05)), slices_margin_cached); } +#else + diff_polygons = diff(diff_polygons, slices_margin_cached); +#endif } polygons_append(contact_polygons, diff_polygons); } // for each layer.region @@ -1597,7 +1601,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Contact layer will be printed with a normal flow, but // it will support layers printed with a bridging flow. - if (SupportMaterialInternal::has_bridging_extrusions(layer)) { + if (m_object_config->thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) { coordf_t bridging_height = 0.; for (const LayerRegion *region : layer.regions()) bridging_height += region->region()->bridging_height_avg(*m_print_config); @@ -1879,12 +1883,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?) // According to Jindrich the bottom surfaces work well. //FIXME test the bridging flow instead? - m_support_material_interface_flow.nozzle_diameter; + m_object_config->thick_bridges.value ? m_support_material_interface_flow.nozzle_diameter() : + // Take the default layer height. + m_object_config->layer_height; layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z : layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; layer_new.bottom_z = layer.print_z; layer_new.idx_object_layer_below = layer_id; - layer_new.bridging = ! m_slicing_params.soluble_interface; + layer_new.bridging = ! m_slicing_params.soluble_interface && m_object_config->thick_bridges; //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); @@ -2512,7 +2518,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( break; polygons_append(polygons_trimming, offset(object_layer.lslices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } - if (! m_slicing_params.soluble_interface) { + if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) { // Collect all bottom surfaces, which will be extruded with a bridging flow. for (; i < object.layers().size(); ++ i) { const Layer &object_layer = *object.layers()[i]; @@ -2810,7 +2816,7 @@ std::pairentities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); + extrusion_entities_append_paths(eec->entities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); // Fill in the rest. fill_expolygons_generate_paths(eec->entities, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); dst.emplace_back(eec.release()); @@ -3011,8 +3017,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const if (n_contact_loops == 0 || top_contact_layer.empty()) return; - Flow flow = interface_flow_src; - flow.height = float(top_contact_layer.layer->height); + Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); Polygons overhang_polygons; if (top_contact_layer.layer->overhang_polygons != nullptr) @@ -3209,7 +3214,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const extrusion_entities_append_paths( top_contact_layer.extrusions, std::move(loop_lines), - erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height); + erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); } #ifdef SLIC3R_DEBUG @@ -3342,7 +3347,7 @@ void modulate_extrusion_by_overlapping_layers( // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). assert(this_layer.print_z > overlapping_layer.print_z); frag.height = float(this_layer.print_z - overlapping_layer.print_z); - frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f, false).mm3_per_mm(); + frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); #ifdef SLIC3R_DEBUG svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); #endif /* SLIC3R_DEBUG */ @@ -3565,7 +3570,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( //FIXME misusing contact_polygons for support columns. ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); if (! to_infill_polygons.empty()) { - Flow flow(float(m_support_material_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging); + assert(! raft_layer.bridging); + Flow flow(float(m_support_material_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter()); Fill * filler = filler_support.get(); filler->angle = raft_angle_base; filler->spacing = m_support_material_flow.spacing(); @@ -3596,7 +3602,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. filler->spacing = m_support_material_flow.spacing(); - flow = Flow(float(m_support_material_interface_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging); + assert(! raft_layer.bridging); + flow = Flow(float(m_support_material_interface_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter()); density = float(interface_density); } else continue; @@ -3734,11 +3741,10 @@ void PrintObjectSupportMaterial::generate_toolpaths( bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - Flow interface_flow( - float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)), - float(layer_ex.layer->height), - m_support_material_interface_flow.nozzle_diameter, - layer_ex.layer->bridging); + auto interface_flow = layer_ex.layer->bridging ? + Flow::bridging_flow(layer_ex.layer->height, m_support_material_interface_flow.nozzle_diameter()) : + Flow(interface_as_base ? m_support_material_flow.width() : m_support_material_interface_flow.width(), + float(layer_ex.layer->height), m_support_material_interface_flow.nozzle_diameter()); filler_interface->angle = interface_as_base ? // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : @@ -3762,11 +3768,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( Fill *filler = filler_base_interface.get(); //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - Flow interface_flow( - float(base_interface_layer.layer->bridging ? base_interface_layer.layer->height : m_support_material_flow.width), // m_support_material_interface_flow.width)), - float(base_interface_layer.layer->height), - m_support_material_flow.nozzle_diameter, - base_interface_layer.layer->bridging); + assert(! base_interface_layer.layer->bridging); + Flow interface_flow = m_support_material_flow.with_height(float(base_interface_layer.layer->height)); filler->angle = interface_angle; filler->spacing = m_support_material_interface_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density)); @@ -3788,11 +3791,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler->angle = angles[support_layer_id % angles.size()]; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. - Flow flow( - float(base_layer.layer->bridging ? base_layer.layer->height : m_support_material_flow.width), - float(base_layer.layer->height), - m_support_material_flow.nozzle_diameter, - base_layer.layer->bridging); + assert(! base_layer.layer->bridging); + auto flow = m_support_material_flow.with_height(float(base_layer.layer->height)); filler->spacing = m_support_material_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); float density = float(support_density); diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 8621586f8..0e5e10d45 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -148,74 +148,33 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle speed_normal = first_layer_speed.get_abs_value(speed_normal); return (speed_normal > 0.) ? speed_normal : speed_max; }; + auto test_flow = + [first_layer_extrusion_width_ptr, extrusion_width, nozzle_diameter, lh, bridging, bridge_speed, bridge_flow_ratio, limit_by_first_layer_speed, max_print_speed, &max_flow, &max_flow_extrusion_type] + (FlowRole flow_role, const ConfigOptionFloatOrPercent &this_extrusion_width, double speed, const char *err_msg) { + Flow flow = bridging ? + Flow::new_from_config_width(flow_role, first_positive(first_layer_extrusion_width_ptr, this_extrusion_width, extrusion_width), nozzle_diameter, lh) : + Flow::bridging_flow(nozzle_diameter * bridge_flow_ratio, nozzle_diameter); + double volumetric_flow = flow.mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(speed, max_print_speed)); + if (max_flow < volumetric_flow) { + max_flow = volumetric_flow; + max_flow_extrusion_type = _utf8(err_msg); + } + }; if (perimeter_extruder_active) { - double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter, - first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * - (bridging ? bridge_speed : - limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed)); - if (max_flow < external_perimeter_rate) { - max_flow = external_perimeter_rate; - max_flow_extrusion_type = _utf8(L("external perimeters")); - } - double perimeter_rate = Flow::new_from_config_width(frPerimeter, - first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * - (bridging ? bridge_speed : - limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed)); - if (max_flow < perimeter_rate) { - max_flow = perimeter_rate; - max_flow_extrusion_type = _utf8(L("perimeters")); - } - } - if (! bridging && infill_extruder_active) { - double infill_rate = Flow::new_from_config_width(frInfill, - first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed); - if (max_flow < infill_rate) { - max_flow = infill_rate; - max_flow_extrusion_type = _utf8(L("infill")); - } + test_flow(frExternalPerimeter, external_perimeter_extrusion_width, std::max(external_perimeter_speed, small_perimeter_speed), L("external perimeters")); + test_flow(frPerimeter, perimeter_extrusion_width, std::max(perimeter_speed, small_perimeter_speed), L("perimeters")); } + if (! bridging && infill_extruder_active) + test_flow(frInfill, infill_extrusion_width, infill_speed, L("infill")); if (solid_infill_extruder_active) { - double solid_infill_rate = Flow::new_from_config_width(frInfill, - first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width), - nozzle_diameter, lh, 0).mm3_per_mm() * - (bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed)); - if (max_flow < solid_infill_rate) { - max_flow = solid_infill_rate; - max_flow_extrusion_type = _utf8(L("solid infill")); - } - if (! bridging) { - double top_solid_infill_rate = Flow::new_from_config_width(frInfill, - first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed); - if (max_flow < top_solid_infill_rate) { - max_flow = top_solid_infill_rate; - max_flow_extrusion_type = _utf8(L("top solid infill")); - } - } - } - if (support_material_extruder_active) { - double support_material_rate = Flow::new_from_config_width(frSupportMaterial, - first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * - (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed)); - if (max_flow < support_material_rate) { - max_flow = support_material_rate; - max_flow_extrusion_type = _utf8(L("support")); - } - } - if (support_material_interface_extruder_active) { - double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface, - first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * - (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed)); - if (max_flow < support_material_interface_rate) { - max_flow = support_material_interface_rate; - max_flow_extrusion_type = _utf8(L("support interface")); - } + test_flow(frInfill, solid_infill_extrusion_width, solid_infill_speed, L("solid infill")); + if (! bridging) + test_flow(frInfill, top_infill_extrusion_width, top_solid_infill_speed, L("top solid infill")); } + if (! bridging && support_material_extruder_active) + test_flow(frSupportMaterial, support_material_extrusion_width, support_material_speed, L("support")); + if (support_material_interface_extruder_active) + test_flow(frSupportMaterialInterface, support_material_extrusion_width, support_material_interface_speed, L("support interface")); //FIXME handle gap_fill_speed if (! out.empty()) out += "\n"; @@ -254,11 +213,11 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre Flow external_perimeter_flow = Flow::new_from_config_width( frExternalPerimeter, *print_config.opt("external_perimeter_extrusion_width"), - nozzle_diameter, layer_height, false); + nozzle_diameter, layer_height); Flow perimeter_flow = Flow::new_from_config_width( frPerimeter, *print_config.opt("perimeter_extrusion_width"), - nozzle_diameter, layer_height, false); + nozzle_diameter, layer_height); if (num_perimeters > 0) { @@ -266,7 +225,7 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre out += (boost::format(_utf8(L("Recommended object thin wall thickness for layer height %.2f and"))) % layer_height).str() + " "; // Start with the width of two closely spaced try { - double width = external_perimeter_flow.width + external_perimeter_flow.spacing(); + double width = external_perimeter_flow.width() + external_perimeter_flow.spacing(); for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) { if (i > 2) out += ", "; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d737bae10..645d31f4b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1433,6 +1433,7 @@ void TabPrint::build() optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters"); optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour"); optgroup->append_single_option_line("thin_walls", category_path + "detect-thin-walls"); + optgroup->append_single_option_line("thick_bridges", category_path + "thick_bridges"); optgroup->append_single_option_line("overhangs", category_path + "detect-bridging-perimeters"); optgroup = page->new_optgroup(L("Advanced")); diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index 969ae3c82..63b62a4d7 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -143,7 +143,7 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { GIVEN("Input spacing of 0.414159 and a total width of 2") { double in_spacing = 0.414159; double total_width = 2.0; - auto flow = Flow::new_from_spacing(1.0, 0.4, 0.3, false); + auto flow = Flow::new_from_spacing(1.0, 0.4, 0.3); WHEN("solid_spacing() is called") { double result = flow.solid_spacing(total_width, in_spacing); THEN("Yielded spacing is greater than 0") { From 8e27e355c2bb8450459d6f031cddab968bd67a3f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 8 Mar 2021 14:29:23 +0100 Subject: [PATCH 41/60] Fixing unit tests. --- t/support.t | 1 - tests/fff_print/test_flow.cpp | 51 ++++--------------- .../test_elephant_foot_compensation.cpp | 22 ++++---- xs/lib/Slic3r/XS.pm | 3 +- xs/xsp/Flow.xsp | 21 +++----- xs/xsp/Layer.xsp | 4 +- xs/xsp/Print.xsp | 3 -- 7 files changed, 30 insertions(+), 75 deletions(-) diff --git a/t/support.t b/t/support.t index a0cac1470..0283df22b 100644 --- a/t/support.t +++ b/t/support.t @@ -31,7 +31,6 @@ use Slic3r::Test; role => FLOW_ROLE_SUPPORT_MATERIAL, nozzle_diameter => $print->config->nozzle_diameter->[$object_config->support_material_extruder-1] // $print->config->nozzle_diameter->[0], layer_height => $object_config->layer_height, - bridge_flow_ratio => 0, ); my $support = Slic3r::Print::SupportMaterial->new( object_config => $print->print->objects->[0]->config, diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index 63b62a4d7..b679a07e3 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -96,43 +96,41 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { ConfigOptionFloatOrPercent width(1.0, false); float nozzle_diameter = 0.4f; - float bridge_flow = 0.f; float layer_height = 0.5f; // Spacing for non-bridges is has some overlap THEN("External perimeter flow has spacing fixed to 1.125 * nozzle_diameter") { - auto flow = Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, bridge_flow); + auto flow = Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); REQUIRE(flow.spacing() == Approx(1.125 * nozzle_diameter - layer_height * (1.0 - PI / 4.0))); } THEN("Internal perimeter flow has spacing fixed to 1.125 * nozzle_diameter") { - auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, bridge_flow); + auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); REQUIRE(flow.spacing() == Approx(1.125 *nozzle_diameter - layer_height * (1.0 - PI / 4.0))); } THEN("Spacing for supplied width is 0.8927f") { - auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, bridge_flow); + auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height); REQUIRE(flow.spacing() == Approx(width.value - layer_height * (1.0 - PI / 4.0))); - flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, bridge_flow); + flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height); REQUIRE(flow.spacing() == Approx(width.value - layer_height * (1.0 - PI / 4.0))); } } /// Check the min/max GIVEN("Nozzle Diameter of 0.25") { float nozzle_diameter = 0.25f; - float bridge_flow = 0.f; float layer_height = 0.5f; WHEN("layer height is set to 0.2") { layer_height = 0.15f; THEN("Max width is set.") { - auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, bridge_flow); - REQUIRE(flow.width == Approx(1.125 * nozzle_diameter)); + auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); + REQUIRE(flow.width() == Approx(1.125 * nozzle_diameter)); } } WHEN("Layer height is set to 0.2") { layer_height = 0.3f; THEN("Min width is set.") { - auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, bridge_flow); - REQUIRE(flow.width == Approx(1.125 * nozzle_diameter)); + auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); + REQUIRE(flow.width() == Approx(1.125 * nozzle_diameter)); } } } @@ -158,41 +156,12 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { /// Spacing, width calculation for bridge extrusions SCENARIO("Flow: Flow math for bridges", "[Flow]") { GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { - auto width = ConfigOptionFloatOrPercent(1.0, false); float nozzle_diameter = 0.4f; float bridge_flow = 1.0f; - float layer_height = 0.5f; WHEN("Flow role is frExternalPerimeter") { - auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, bridge_flow); + auto flow = Flow::bridging_flow(nozzle_diameter * sqrt(bridge_flow), nozzle_diameter); THEN("Bridge width is same as nozzle diameter") { - REQUIRE(flow.width == Approx(nozzle_diameter)); - } - THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") { - REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING)); - } - } - WHEN("Flow role is frInfill") { - auto flow = Flow::new_from_config_width(frInfill, width, nozzle_diameter, layer_height, bridge_flow); - THEN("Bridge width is same as nozzle diameter") { - REQUIRE(flow.width == Approx(nozzle_diameter)); - } - THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") { - REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING)); - } - } - WHEN("Flow role is frPerimeter") { - auto flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, bridge_flow); - THEN("Bridge width is same as nozzle diameter") { - REQUIRE(flow.width == Approx(nozzle_diameter)); - } - THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") { - REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING)); - } - } - WHEN("Flow role is frSupportMaterial") { - auto flow = Flow::new_from_config_width(frSupportMaterial, width, nozzle_diameter, layer_height, bridge_flow); - THEN("Bridge width is same as nozzle diameter") { - REQUIRE(flow.width == Approx(nozzle_diameter)); + REQUIRE(flow.width() == Approx(nozzle_diameter)); } THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") { REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING)); diff --git a/tests/libslic3r/test_elephant_foot_compensation.cpp b/tests/libslic3r/test_elephant_foot_compensation.cpp index 180f678c5..4e340c37a 100644 --- a/tests/libslic3r/test_elephant_foot_compensation.cpp +++ b/tests/libslic3r/test_elephant_foot_compensation.cpp @@ -434,7 +434,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { ExPolygon expoly = contour_with_hole(); WHEN("Compensated") { // Elephant foot compensation shall not pinch off bits from this contour. - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.2f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.2f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_with_hole.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -449,7 +449,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { GIVEN("Tiny contour") { ExPolygon expoly({ { 133382606, 94912473 }, { 134232493, 95001115 }, { 133783926, 95159440 }, { 133441897, 95180666 }, { 133408242, 95191984 }, { 133339012, 95166830 }, { 132991642, 95011087 }, { 133206549, 94908304 } }); WHEN("Compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.2f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.2f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_tiny.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -464,7 +464,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { GIVEN("Large box") { ExPolygon expoly( { {50000000, 50000000 }, { 0, 50000000 }, { 0, 0 }, { 50000000, 0 } } ); WHEN("Compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.21f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.21f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_large_box.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -479,7 +479,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { GIVEN("Thin ring (GH issue #2085)") { ExPolygon expoly = thin_ring(); WHEN("Compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.25f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.25f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_thin_ring.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -532,7 +532,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { expoly = union_ex({ expoly, expoly2 }).front(); WHEN("Partially compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.25f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.25f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_0.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -543,7 +543,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { } } WHEN("Fully compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f, false), 0.17f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f), 0.17f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_1.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -558,7 +558,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { GIVEN("Box with hole close to wall (GH issue #2998)") { ExPolygon expoly = box_with_hole_close_to_wall(); WHEN("Compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.25f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.25f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_2.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -575,7 +575,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { ExPolygon expoly = spirograph_gear_1mm(); WHEN("Partially compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.25f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.25f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_2.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -586,7 +586,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { } } WHEN("Fully compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f, false), 0.17f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f), 0.17f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_3.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -597,7 +597,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { } } WHEN("Brutally compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.6f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.6f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_4.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, @@ -612,7 +612,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { GIVEN("Vase with fins") { ExPolygon expoly = vase_with_fins(); WHEN("Compensated") { - ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.41f); + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.41f); #ifdef TESTS_EXPORT_SVGS SVG::export_expolygons(debug_out_path("elephant_foot_compensation_vase_with_fins.svg").c_str(), { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index fa4dde43a..6d3bf35cf 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -158,7 +158,6 @@ sub new { my $self = $class->_new( @args{qw(width height nozzle_diameter)}, ); - $self->set_bridge($args{bridge} // 0); return $self; } @@ -166,7 +165,7 @@ sub new_from_width { my ($class, %args) = @_; return $class->_new_from_width( - @args{qw(role width nozzle_diameter layer_height bridge_flow_ratio)}, + @args{qw(role width nozzle_diameter layer_height)}, ); } diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp index b57df5e37..6962085d5 100644 --- a/xs/xsp/Flow.xsp +++ b/xs/xsp/Flow.xsp @@ -8,21 +8,13 @@ %name{Slic3r::Flow} class Flow { ~Flow(); %name{_new} Flow(float width, float height, float nozzle_diameter); - void set_height(float height) - %code{% THIS->height = height; %}; - void set_bridge(bool bridge) - %code{% THIS->bridge = bridge; %}; Clone clone() %code{% RETVAL = THIS; %}; - float width() - %code{% RETVAL = THIS->width; %}; - float height() - %code{% RETVAL = THIS->height; %}; - float nozzle_diameter() - %code{% RETVAL = THIS->nozzle_diameter; %}; - bool bridge() - %code{% RETVAL = THIS->bridge; %}; + float width(); + float height(); + float nozzle_diameter(); + bool bridge(); float spacing(); float spacing_to(Flow* other) %code{% RETVAL = THIS->spacing(*other); %}; @@ -32,17 +24,16 @@ %{ Flow* -_new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio) +_new_from_width(CLASS, role, width, nozzle_diameter, height) char* CLASS; FlowRole role; std::string width; float nozzle_diameter; float height; - float bridge_flow_ratio; CODE: ConfigOptionFloatOrPercent optwidth; optwidth.deserialize(width); - RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio)); + RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height)); OUTPUT: RETVAL diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index 5d006e676..50ddfd9a1 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -23,8 +23,8 @@ Ref fills() %code%{ RETVAL = &THIS->fills; %}; - Clone flow(FlowRole role, bool bridge = false, double width = -1) - %code%{ RETVAL = THIS->flow(role, bridge, width); %}; + Clone flow(FlowRole role) + %code%{ RETVAL = THIS->flow(role); %}; void prepare_fill_surfaces(); void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) %code%{ THIS->make_perimeters(*slices, fill_surfaces); %}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index cc3dac224..9e632bd53 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -33,9 +33,6 @@ _constant() Ref config() %code%{ RETVAL = &THIS->config(); %}; Ref print(); - - Clone flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, PrintObject* object) - %code%{ RETVAL = THIS->flow(role, layer_height, bridge, first_layer, width, *object); %}; }; %name{Slic3r::Print::Object} class PrintObject { From f01f02154c617cc24835475da419ed6322f2ceec Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 9 Mar 2021 12:30:31 +0100 Subject: [PATCH 42/60] Reworked the "new" bridging to respect the bridge_flow_ratio by maintaining extrusion spacing, but modifying the extrusion width and / or height. --- src/libslic3r/Fill/Fill.cpp | 33 ++++---- src/libslic3r/Flow.cpp | 114 +++++++++++++++------------ src/libslic3r/Flow.hpp | 64 +++++++++------ src/libslic3r/Layer.hpp | 1 + src/libslic3r/LayerRegion.cpp | 23 +++++- src/libslic3r/PerimeterGenerator.cpp | 2 +- src/libslic3r/Print.hpp | 1 - src/libslic3r/PrintObject.cpp | 15 ++-- src/libslic3r/PrintRegion.cpp | 11 --- src/libslic3r/SupportMaterial.cpp | 40 +++++----- src/libslic3r/SupportMaterial.hpp | 1 + xs/xsp/Flow.xsp | 2 - 12 files changed, 173 insertions(+), 134 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 129a9440c..579259a5f 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -28,6 +28,8 @@ struct SurfaceFillParams // coordf_t overlap = 0.; // Angle as provided by the region config, in radians. float angle = 0.f; + // Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set. + bool bridge; // Non-negative for a bridge. float bridge_angle = 0.f; @@ -73,18 +75,19 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(flow.width()); RETURN_COMPARE_NON_EQUAL(flow.height()); RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter()); - RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge()); + RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge); RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role); return false; } bool operator==(const SurfaceFillParams &rhs) const { return this->extruder == rhs.extruder && - this->pattern == rhs.pattern && this->pattern == rhs.pattern && this->spacing == rhs.spacing && // this->overlap == rhs.overlap && this->angle == rhs.angle && + this->bridge == rhs.bridge && +// this->bridge_angle == rhs.bridge_angle && this->density == rhs.density && // this->dont_adjust == rhs.dont_adjust && this->anchor_length == rhs.anchor_length && @@ -128,6 +131,7 @@ std::vector group_fills(const Layer &layer) if (surface.is_solid()) { params.density = 100.f; + //FIXME for non-thick bridges, shall we allow a bottom surface pattern? params.pattern = (surface.is_external() && ! is_bridge) ? (surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) : region_config.top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; @@ -144,9 +148,10 @@ std::vector group_fills(const Layer &layer) params.angle = float(Geometry::deg2rad(region_config.fill_angle.value)); // Calculate the actual flow we'll be using for this infill. - params.flow = is_bridge || Fill::use_bridge_flow(params.pattern) ? + params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); + params.flow = params.bridge ? layerm.bridging_flow(extrusion_role) : - layerm.region()->flow(*layer.object(), extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness, layer.id() == 0); + layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness); // Calculate flow spacing for infill pattern generation. if (surface.is_solid() || is_bridge) { @@ -159,7 +164,7 @@ std::vector group_fills(const Layer &layer) // for all layers, for avoiding the ugly effect of // misaligned infill on first layer because of different extrusion width and // layer height - params.spacing = layerm.region()->flow(*layer.object(), frInfill, layer.object()->config().layer_height).spacing(); + params.spacing = layerm.flow(frInfill, layer.object()->config().layer_height).spacing(); // Anchor a sparse infill to inner perimeters with the following anchor length: params.anchor_length = float(region_config.infill_anchor); if (region_config.infill_anchor.percent) @@ -278,7 +283,7 @@ std::vector group_fills(const Layer &layer) params.extrusion_role = erInternalInfill; params.angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); // calculate the actual flow we'll be using for this infill - params.flow = layerm.region()->flow(*layer.object(), frSolidInfill, layer.height, layer.id() == 0); + params.flow = layerm.flow(frSolidInfill); params.spacing = params.flow.spacing(); surface_fills.emplace_back(params); surface_fills.back().surface.surface_type = stInternalSolid; @@ -346,9 +351,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; // calculate flow spacing for infill pattern generation - bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge(); + bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; double link_max_length = 0.; - if (! surface_fill.params.flow.bridge()) { + if (! surface_fill.params.bridge) { #if 0 link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); // printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length); @@ -389,9 +394,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // so we can safely ignore the slight variation that might have // been applied to f->spacing } else { - Flow new_flow = surface_fill.params.flow.bridge() ? - Flow::bridging_flow_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter()) : - Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter(), surface_fill.params.flow.height()); + Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing)); flow_mm3_per_mm = new_flow.mm3_per_mm(); flow_width = new_flow.width(); } @@ -601,9 +604,9 @@ void Layer::make_ironing() fill.spacing = ironing_params.line_spacing; fill.angle = float(ironing_params.angle + 0.25 * M_PI); fill.link_max_length = (coord_t)scale_(3. * fill.spacing); - double height = ironing_params.height * fill.spacing / nozzle_dmr; - Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height)); - double flow_mm3_per_mm = flow.mm3_per_mm(); + double extrusion_height = ironing_params.height * fill.spacing / nozzle_dmr; + float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height)); + double flow_mm3_per_mm = nozzle_dmr * extrusion_height; Surface surface_fill(stTop, ExPolygon()); for (ExPolygon &expoly : ironing_areas) { surface_fill.expolygon = std::move(expoly); @@ -621,7 +624,7 @@ void Layer::make_ironing() extrusion_entities_append_paths( eec->entities, std::move(polylines), erIroning, - flow_mm3_per_mm, float(flow.width()), float(height)); + flow_mm3_per_mm, extrusion_width, float(extrusion_height)); } } } diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 103123cdb..6e54a517c 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -6,12 +6,6 @@ #include -// Overlap factor of perimeter lines. Currently no overlap. -// #define HAS_PERIMETER_LINE_OVERLAP -#ifdef HAS_PERIMETER_LINE_OVERLAP - #define PERIMETER_LINE_OVERLAP_FACTOR 1.0 -#endif - // Mark string for localization and translate. #define L(s) Slic3r::I18N::translate(s) @@ -142,62 +136,82 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent w = float(width.get_abs_value(height)); } - return Flow(w, height, nozzle_diameter, false); + return Flow(w, height, rounded_rectangle_extrusion_spacing(w, height), nozzle_diameter, false); } -// This constructor builds a Flow object from a given centerline spacing. -Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height) +// Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions. +Flow Flow::with_spacing(float new_spacing) const { - if (height <= 0) - throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()"); - // Calculate width from spacing. - // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions. - float width = float( -#ifdef HAS_PERIMETER_LINE_OVERLAP - (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI)); -#else - (spacing + height * (1. - 0.25 * PI))); -#endif - return Flow(width, height, nozzle_diameter); + Flow out = *this; + if (m_bridge) { + // Diameter of the rounded extrusion. + assert(m_width == m_height); + float gap = m_spacing - m_width; + auto new_diameter = new_spacing - gap; + out.m_width = out.m_height = new_diameter; + } else { + out.m_width += new_spacing - m_spacing; + if (out.m_width < out.m_height) + throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing()"); + } + out.m_spacing = new_spacing; + return out; } -// This method returns the centerline spacing between two adjacent extrusions -// having the same extrusion width (and other properties). -float Flow::spacing() const +// Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing. +Flow Flow::with_cross_section(float area_new) const { -#ifdef HAS_PERIMETER_LINE_OVERLAP - if (m_bridge) - return m_width + BRIDGE_EXTRA_SPACING; - // rectangle with semicircles at the ends - float min_flow_spacing = m_width - m_height * (1. - 0.25 * PI); - float res = m_width - PERIMETER_LINE_OVERLAP_FACTOR * (m_width - min_flow_spacing); -#else - float res = float(m_bridge ? (m_width + BRIDGE_EXTRA_SPACING) : (m_width - m_height * (1. - 0.25 * PI))); -#endif -// assert(res > 0.f); - if (res <= 0.f) - throw FlowErrorNegativeSpacing(); - return res; + assert(! m_bridge); + assert(flow.width() >= flow.height()); + + // Adjust for bridge_flow_ratio, maintain the extrusion spacing. + float area = this->mm3_per_mm(); + if (area_new > area + EPSILON) { + // Increasing the flow rate. + float new_full_spacing = area_new / m_height; + if (new_full_spacing > m_spacing) { + // Filling up the spacing without an air gap. Grow the extrusion in height. + float height = area_new / m_spacing; + return Flow(rounded_rectangle_extrusion_width_from_spacing(m_spacing, height), height, m_spacing, m_nozzle_diameter, false); + } else { + return this->with_width(rounded_rectangle_extrusion_width_from_spacing(area / m_height, m_height)); + } + } else if (area_new < area - EPSILON) { + // Decreasing the flow rate. + float width_new = m_width - (area - area_new) / m_height; + assert(width_dif > 0); + if (width_new > m_height) { + // Shrink the extrusion width. + return this->with_width(width_new); + } else { + // Create a rounded extrusion. + auto dmr = float(sqrt(area_new / M_PI)); + return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, false); + } + } else + return *this; } -// This method returns the centerline spacing between an extrusion using this -// flow and another one using another flow. -// this->spacing(other) shall return the same value as other.spacing(*this) -float Flow::spacing(const Flow &other) const +float Flow::rounded_rectangle_extrusion_spacing(float width, float height) { - assert(m_height == other.m_height); - assert(m_bridge == other.m_bridge); - float res = float(m_bridge ? - 0.5 * m_width + 0.5 * other.m_width + BRIDGE_EXTRA_SPACING : - 0.5 * this->spacing() + 0.5 * other.spacing()); -// assert(res > 0.f); - if (res <= 0.f) - throw FlowErrorNegativeSpacing(); - return res; + auto out = width - height * float(1. - 0.25 * PI); + if (out <= 0.f) + throw FlowErrorNegativeSpacing(); + return out; +} + +float Flow::rounded_rectangle_extrusion_width_from_spacing(float spacing, float height) +{ + return float(spacing + height * (1. - 0.25 * PI)); +} + +float Flow::bridge_extrusion_spacing(float dmr) +{ + return dmr + BRIDGE_EXTRA_SPACING; } // This method returns extrusion volume per head move unit. -double Flow::mm3_per_mm() const +double Flow::mm3_per_mm() const { float res = m_bridge ? // Area of a circle with dmr of this->width. diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 101dc6880..937fb176a 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -51,26 +51,26 @@ public: class Flow { public: + Flow() = default; + Flow(float width, float height, float nozzle_diameter) : + Flow(width, height, rounded_rectangle_extrusion_spacing(width, height), nozzle_diameter, false) {} + // Non bridging flow: Maximum width of an extrusion with semicircles at the ends. // Bridging flow: Bridge thread diameter. - float width() const { return m_width; } + float width() const { return m_width; } + coord_t scaled_width() const { return coord_t(scale_(m_width)); } // Non bridging flow: Layer height. // Bridging flow: Bridge thread diameter = layer height. - float height() const { return m_height; } + float height() const { return m_height; } + // Spacing between the extrusion centerlines. + float spacing() const { return m_spacing; } + coord_t scaled_spacing() const { return coord_t(scale_(m_spacing)); } // Nozzle diameter. - float nozzle_diameter() const { return m_nozzle_diameter; } + float nozzle_diameter() const { return m_nozzle_diameter; } // Is it a bridge? - bool bridge() const { return m_bridge; } - - Flow() = default; - Flow(float w, float h, float nozzle_diameter) : Flow(w, h, nozzle_diameter, false) {} - - float spacing() const; - float spacing(const Flow &other) const; - double mm3_per_mm() const; - coord_t scaled_width() const { return coord_t(scale_(m_width)); } - coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); } - coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); } + bool bridge() const { return m_bridge; } + // Cross section area of the extrusion. + double mm3_per_mm() const; // Elephant foot compensation spacing to be used to detect narrow parts, where the elephant foot compensation cannot be applied. // To be used on frExternalPerimeter only. @@ -80,18 +80,30 @@ public: bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; } - Flow with_width (float width) const { assert(! m_bridge); return Flow(width, m_height, m_nozzle_diameter, m_bridge); } - Flow with_height(float height) const { assert(! m_bridge); return Flow(m_width, height, m_nozzle_diameter, m_bridge); } + Flow with_width (float width) const { + assert(! m_bridge); + return Flow(width, m_height, rounded_rectangle_extrusion_spacing(width, m_height), m_nozzle_diameter, m_bridge); + } + Flow with_height(float height) const { + assert(! m_bridge); + return Flow(m_width, height, rounded_rectangle_extrusion_spacing(m_width, height), m_nozzle_diameter, m_bridge); + } + // Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions. + Flow with_spacing(float spacing) const; + // Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing. + Flow with_cross_section(float area) const; + Flow with_flow_ratio(double ratio) const { return this->with_cross_section(this->mm3_per_mm() * ratio); } + + static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, bridge_extrusion_spacing(dmr), nozzle_diameter, true }; } - static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, nozzle_diameter, true }; } - static Flow bridging_flow_from_spacing(float spacing, float nozzle_diameter) - { auto dmr = spacing - float(BRIDGE_EXTRA_SPACING); return Flow { dmr, dmr, nozzle_diameter, true }; } - static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height); - // Create a flow from the spacing of extrusion lines. - // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale - // to fit a region with integer number of lines. - static Flow new_from_spacing(float spacing, float nozzle_diameter, float height); + + // Spacing of extrusions with rounded extrusion model. + static float rounded_rectangle_extrusion_spacing(float width, float height); + // Width of extrusions with rounded extrusion model. + static float rounded_rectangle_extrusion_width_from_spacing(float spacing, float height); + // Spacing of round thread extrusions. + static float bridge_extrusion_spacing(float dmr); // Sane extrusion width defautl based on nozzle diameter. // The defaults were derived from manual Prusa MK3 profiles. @@ -104,10 +116,12 @@ public: static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0); private: - Flow(float w, float h, float nozzle_diameter, bool bridge) : m_width(w), m_height(h), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge) {} + Flow(float width, float height, float spacing, float nozzle_diameter, bool bridge) : + m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge) {} float m_width { 0 }; float m_height { 0 }; + float m_spacing { 0 }; float m_nozzle_diameter { 0 }; bool m_bridge { false }; }; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index ef3124dcb..8e2348530 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -60,6 +60,7 @@ public: ExtrusionEntityCollection fills; Flow flow(FlowRole role) const; + Flow flow(FlowRole role, double layer_height) const; Flow bridging_flow(FlowRole role) const; void slices_to_fill_surfaces_clipped(); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 4b8fcaef9..1bca95ca3 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -17,12 +17,29 @@ namespace Slic3r { Flow LayerRegion::flow(FlowRole role) const { - return m_region->flow(*m_layer->object(), role, m_layer->height, m_layer->id() == 0); + return this->flow(role, m_layer->height); +} + +Flow LayerRegion::flow(FlowRole role, double layer_height) const +{ + return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0); } Flow LayerRegion::bridging_flow(FlowRole role) const -{ - return this->layer()->object()->config().thick_bridges ? m_region->bridging_flow(role) : this->flow(role); +{ + const PrintRegion ®ion = *this->region(); + const PrintRegionConfig ®ion_config = region.config(); + if (this->layer()->object()->config().thick_bridges) { + // The old Slic3r way (different from all other slicers): Use rounded extrusions. + // Get the configured nozzle_diameter for the extruder associated to the flow role requested. + // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. + auto nozzle_diameter = float(region.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); + // Applies default bridge spacing. + return Flow::bridging_flow(float(sqrt(region_config.bridge_flow_ratio)) * nozzle_diameter, nozzle_diameter); + } else { + // The same way as other slicers: Use normal extrusions. Apply bridge_flow_ratio while maintaining the original spacing. + return this->flow(role).with_flow_ratio(region_config.bridge_flow_ratio); + } } // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index d33e1cfd4..6ec4dbf6b 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -286,7 +286,7 @@ void PerimeterGenerator::process() m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow); + coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 04cf58fa9..91f86d010 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -66,7 +66,6 @@ public: // 1-based extruder identifier for this region and role. unsigned int extruder(FlowRole role) const; Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const; - Flow bridging_flow(FlowRole role) const; // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; // Average diameter of nozzles participating on extruding this region. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 593955576..3fe5f2421 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1457,19 +1457,18 @@ void PrintObject::bridge_over_infill() const PrintRegion ®ion = *m_print->regions()[region_id]; // skip bridging in case there are no voids - if (region.config().fill_density.value == 100) continue; - - // get bridge flow - Flow bridge_flow = region.bridging_flow(frSolidInfill); - + if (region.config().fill_density.value == 100) + continue; + for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) { // skip first layer if (layer_it == m_layers.begin()) continue; - Layer* layer = *layer_it; - LayerRegion* layerm = layer->m_regions[region_id]; - + Layer *layer = *layer_it; + LayerRegion *layerm = layer->m_regions[region_id]; + Flow bridge_flow = layerm->bridging_flow(frSolidInfill); + // extract the stInternalSolid surfaces that might be transformed into bridges Polygons internal_solid; layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index acb429821..837200984 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -48,17 +48,6 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); } -Flow PrintRegion::bridging_flow(FlowRole role) const -{ - // Get the configured nozzle_diameter for the extruder associated to the flow role requested. - // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1)); - double bfr = m_config.bridge_flow_ratio; - if (bfr != 1.) - bfr = sqrt(bfr); - return Flow::bridging_flow(float(bfr) * nozzle_diameter, nozzle_diameter); -} - coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const { return (print_config.nozzle_diameter.get_at(m_config.perimeter_extruder.value - 1) + diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 7c018c974..b68d1f2c3 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -334,7 +334,7 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object for (auto lh : m_print_config->min_layer_height.values) m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh)); - if (m_object_config->support_material_interface_layers.value == 0) { + if (m_slicing_params.soluble_interface) { // No interface layers allowed, print everything with the base support pattern. m_support_material_interface_flow = m_support_material_flow; } @@ -342,11 +342,21 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object // Evaluate the XY gap between the object outer perimeters and the support structures. // Evaluate the XY gap between the object outer perimeters and the support structures. coordf_t external_perimeter_width = 0.; + size_t num_nonempty_regions = 0; + coordf_t bridge_flow_ratio = 0; for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) - if (! object->region_volumes[region_id].empty()) - external_perimeter_width = std::max(external_perimeter_width, - (coordf_t)object->print()->get_region(region_id)->flow(*object, frExternalPerimeter, slicing_params.layer_height).width()); + if (! object->region_volumes[region_id].empty()) { + ++ num_nonempty_regions; + const PrintRegion ®ion = *object->print()->get_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); + bridge_flow_ratio += region.config().bridge_flow_ratio; + } m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); + bridge_flow_ratio /= num_nonempty_regions; + + m_support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? + m_support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : + Flow::bridging_flow(bridge_flow_ratio * m_support_material_interface_flow.nozzle_diameter(), m_support_material_interface_flow.nozzle_diameter()); m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; if (! m_can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { @@ -1231,8 +1241,8 @@ namespace SupportMaterialInternal { // since we're dealing with bridges, we can't assume width is larger than spacing, // so we take the largest value and also apply safety offset to be ensure no gaps // are left in between - Flow bridge_flow = layerm->bridging_flow(frPerimeter); - float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); + Flow perimeter_bridge_flow = layerm->bridging_flow(frPerimeter); + float w = float(std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())); for (Polyline &polyline : overhang_perimeters) if (polyline.is_straight()) { // This is a bridge @@ -1879,13 +1889,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta layer_new.height = m_slicing_params.soluble_interface ? // Align the interface layer with the object's layer height. object.layers()[layer_id + 1]->height : - // Place a bridge flow interface layer over the top surface. - //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?) - // According to Jindrich the bottom surfaces work well. - //FIXME test the bridging flow instead? - m_object_config->thick_bridges.value ? m_support_material_interface_flow.nozzle_diameter() : - // Take the default layer height. - m_object_config->layer_height; + // Place a bridge flow interface layer or the normal flow interface layer over the top surface. + m_support_material_bottom_interface_flow.height(); layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z : layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; layer_new.bottom_z = layer.print_z; @@ -3497,7 +3502,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing); - if (m_object_config->support_material_interface_layers.value == 0) { + if (m_slicing_params.soluble_interface) { // No interface layers allowed, print everything with the base support pattern. interface_spacing = support_spacing; interface_density = support_density; @@ -3738,13 +3743,12 @@ void PrintObjectSupportMaterial::generate_toolpaths( MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) continue; - bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; + bool interface_as_base = (&layer_ex == &interface_layer) && m_slicing_params.soluble_interface; //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) auto interface_flow = layer_ex.layer->bridging ? - Flow::bridging_flow(layer_ex.layer->height, m_support_material_interface_flow.nozzle_diameter()) : - Flow(interface_as_base ? m_support_material_flow.width() : m_support_material_interface_flow.width(), - float(layer_ex.layer->height), m_support_material_interface_flow.nozzle_diameter()); + Flow::bridging_flow(layer_ex.layer->height, m_support_material_bottom_interface_flow.nozzle_diameter()) : + (interface_as_base ? &m_support_material_flow : &m_support_material_interface_flow)->with_height(float(layer_ex.layer->height)); filler_interface->angle = interface_as_base ? // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 7123290a6..da13768f6 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -244,6 +244,7 @@ private: Flow m_first_layer_flow; Flow m_support_material_flow; Flow m_support_material_interface_flow; + Flow m_support_material_bottom_interface_flow; // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? bool m_can_merge_support_regions; diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp index 6962085d5..019af16f2 100644 --- a/xs/xsp/Flow.xsp +++ b/xs/xsp/Flow.xsp @@ -16,8 +16,6 @@ float nozzle_diameter(); bool bridge(); float spacing(); - float spacing_to(Flow* other) - %code{% RETVAL = THIS->spacing(*other); %}; int scaled_width(); int scaled_spacing(); double mm3_per_mm(); From adcbe4347ccd485a273f81687b8456c63784e0c3 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 9 Mar 2021 13:54:42 +0100 Subject: [PATCH 43/60] Fixed unit tests. --- src/libslic3r/Flow.cpp | 5 +++-- src/libslic3r/Flow.hpp | 3 ++- src/libslic3r/GCode.cpp | 3 ++- tests/fff_print/test_flow.cpp | 6 +++--- tests/fff_print/test_print.cpp | 14 +++++++------- tests/fff_print/test_printgcode.cpp | 13 +++++++------ 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 6e54a517c..1645bf683 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -150,6 +150,7 @@ Flow Flow::with_spacing(float new_spacing) const auto new_diameter = new_spacing - gap; out.m_width = out.m_height = new_diameter; } else { + assert(m_width >= m_height); out.m_width += new_spacing - m_spacing; if (out.m_width < out.m_height) throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing()"); @@ -162,7 +163,7 @@ Flow Flow::with_spacing(float new_spacing) const Flow Flow::with_cross_section(float area_new) const { assert(! m_bridge); - assert(flow.width() >= flow.height()); + assert(m_width >= m_height); // Adjust for bridge_flow_ratio, maintain the extrusion spacing. float area = this->mm3_per_mm(); @@ -179,7 +180,7 @@ Flow Flow::with_cross_section(float area_new) const } else if (area_new < area - EPSILON) { // Decreasing the flow rate. float width_new = m_width - (area - area_new) / m_height; - assert(width_dif > 0); + assert(width_new > 0); if (width_new > m_height) { // Shrink the extrusion width. return this->with_width(width_new); diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 937fb176a..428d31c1f 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -117,7 +117,8 @@ public: private: Flow(float width, float height, float spacing, float nozzle_diameter, bool bridge) : - m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge) {} + m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge) + { assert(width >= height); } float m_width { 0 }; float m_height { 0 }; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index fd6c82b84..28a68e171 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1815,7 +1815,8 @@ namespace Skirt { // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. std::map> skirt_loops_per_extruder_out; - assert(skirt_done.empty()); + //For sequential print, the following test may fail when extruding the 2nd and other objects. + // assert(skirt_done.empty()); if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt) { skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); skirt_done.emplace_back(layer_tools.print_z); diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index b679a07e3..08ba15a84 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -96,7 +96,7 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { ConfigOptionFloatOrPercent width(1.0, false); float nozzle_diameter = 0.4f; - float layer_height = 0.5f; + float layer_height = 0.4f; // Spacing for non-bridges is has some overlap THEN("External perimeter flow has spacing fixed to 1.125 * nozzle_diameter") { @@ -126,8 +126,8 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { REQUIRE(flow.width() == Approx(1.125 * nozzle_diameter)); } } - WHEN("Layer height is set to 0.2") { - layer_height = 0.3f; + WHEN("Layer height is set to 0.25") { + layer_height = 0.25f; THEN("Min width is set.") { auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); REQUIRE(flow.width() == Approx(1.125 * nozzle_diameter)); diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 3250443ed..fc2ac3dee 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -53,8 +53,8 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t config.set_deserialize({ { "top_solid_layers", 2 }, { "bottom_solid_layers", 1 }, - { "layer_height", 0.5 }, // get a known number of layers - { "first_layer_height", 0.5 } + { "layer_height", 0.25 }, // get a known number of layers + { "first_layer_height", 0.25 } }); Slic3r::Print print; Slic3r::Model model; @@ -72,8 +72,8 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t }; print.process(); test_is_solid_infill(0, 0); // should be solid - test_is_solid_infill(0, 39); // should be solid - test_is_solid_infill(0, 38); // should be solid + test_is_solid_infill(0, 79); // should be solid + test_is_solid_infill(0, 78); // should be solid WHEN("Model is re-sliced with top_solid_layers == 3") { config.set("top_solid_layers", 3); print.apply(model, config); @@ -82,9 +82,9 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t test_is_solid_infill(0, 0); } AND_THEN("Print object has 3 top solid layers") { - test_is_solid_infill(0, 39); - test_is_solid_infill(0, 38); - test_is_solid_infill(0, 37); + test_is_solid_infill(0, 79); + test_is_solid_infill(0, 78); + test_is_solid_infill(0, 77); } } } diff --git a/tests/fff_print/test_printgcode.cpp b/tests/fff_print/test_printgcode.cpp index 10d3af9a3..d6f6eca58 100644 --- a/tests/fff_print/test_printgcode.cpp +++ b/tests/fff_print/test_printgcode.cpp @@ -244,23 +244,24 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") { { "complete_objects", true }, { "gcode_comments", true }, { "layer_gcode", ";Layer:[layer_num] ([layer_z] mm)" }, - { "layer_height", 1.0 }, - { "first_layer_height", 1.0 } + { "layer_height", 0.1 }, + { "first_layer_height", 0.1 } }); // End of the 1st object. - size_t pos = gcode.find(";Layer:19 "); + std::string token = ";Layer:199 "; + size_t pos = gcode.find(token); THEN("First and second object last layer is emitted") { // First object REQUIRE(pos != std::string::npos); - pos += 10; + pos += token.size(); REQUIRE(pos < gcode.size()); double z = 0; REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1)); REQUIRE(z == Approx(20.)); // Second object - pos = gcode.find(";Layer:39 ", pos); + pos = gcode.find(";Layer:399 ", pos); REQUIRE(pos != std::string::npos); - pos += 10; + pos += token.size(); REQUIRE(pos < gcode.size()); REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1)); REQUIRE(z == Approx(20.)); From 00db3dc41937670e91c26170c9c168bf75a86aaf Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 9 Mar 2021 15:29:13 +0100 Subject: [PATCH 44/60] WIP: Splitting the number of top / bottom support interface layers. If the new support_material_bottom_interface_layers is left at default -1, then support_material_interface_layers is used for both top and bottom interface layers. If support_material_interface_layers == 0, then neither top nor bottom interface layers are being extruded. --- src/libslic3r/Flow.hpp | 5 +- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 12 ++- src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/PrintObject.cpp | 1 + src/libslic3r/SupportMaterial.cpp | 105 +++++++++++++++----------- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 81 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 428d31c1f..04ced3e13 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -118,7 +118,10 @@ public: private: Flow(float width, float height, float spacing, float nozzle_diameter, bool bridge) : m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge) - { assert(width >= height); } + { + // Gap fill violates this condition. + //assert(width >= height); + } float m_width { 0 }; float m_height { 0 }; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 81a14eaec..412dbc678 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -428,7 +428,7 @@ const std::vector& Preset::print_options() "min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", - "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", + "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", "support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b87042b89..d744cb2a7 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2299,7 +2299,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(1)); def = this->add("support_material_interface_layers", coInt); - def->label = L("Interface layers"); + def->label = L("Top interface layers"); def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material."); def->sidetext = L("layers"); @@ -2307,6 +2307,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("support_material_bottom_interface_layers", coInt); + def->label = L("Bottom interface layers"); + def->category = L("Support material"); + def->tooltip = L("Number of interface layers to insert between the object(s) and support material. " + "Set to -1 to use support_material_interface_layers"); + def->sidetext = L("layers"); + def->min = -1; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(-1)); + def = this->add("support_material_interface_spacing", coFloat); def->label = L("Interface pattern spacing"); def->category = L("Support material"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c92abf51f..4a4647b73 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -509,6 +509,7 @@ public: ConfigOptionBool support_material_interface_contact_loops; ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; + ConfigOptionInt support_material_bottom_interface_layers; // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. ConfigOptionFloat support_material_interface_spacing; ConfigOptionFloatOrPercent support_material_interface_speed; @@ -560,6 +561,7 @@ protected: OPT_PTR(support_material_extrusion_width); OPT_PTR(support_material_interface_extruder); OPT_PTR(support_material_interface_layers); + OPT_PTR(support_material_bottom_interface_layers); OPT_PTR(support_material_interface_spacing); OPT_PTR(support_material_interface_speed); OPT_PTR(support_material_pattern); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 3fe5f2421..a3b0fb787 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -587,6 +587,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_material_extruder" || opt_key == "support_material_extrusion_width" || opt_key == "support_material_interface_layers" + || opt_key == "support_material_bottom_interface_layers" || opt_key == "support_material_interface_pattern" || opt_key == "support_material_interface_contact_loops" || opt_key == "support_material_interface_extruder" diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index b68d1f2c3..7747559e6 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -2720,17 +2720,23 @@ std::pairsupport_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && // Base extruder: Either "print with active extruder" not soluble. (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); - int num_interface_layers = m_object_config->support_material_interface_layers.value; - int num_base_interface_layers = soluble_interface_non_soluble_base ? std::min(num_interface_layers / 2, 2) : 0; + int num_interface_layers_top = m_object_config->support_material_interface_layers; + int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; + if (num_interface_layers_bottom < 0) + num_interface_layers_bottom = num_interface_layers_top; + int num_base_interface_layers_top = soluble_interface_non_soluble_base ? std::min(num_interface_layers_top / 2, 2) : 0; + int num_base_interface_layers_bottom = soluble_interface_non_soluble_base ? std::min(num_interface_layers_bottom / 2, 2) : 0; - if (! intermediate_layers.empty() && num_interface_layers > 1) { + if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) { // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers. BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; // Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1. - -- num_interface_layers; - int num_interface_layers_only = num_interface_layers - num_base_interface_layers; + -- num_interface_layers_top; + -- num_interface_layers_bottom; + int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top; + int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom; interface_layers.assign(intermediate_layers.size(), nullptr); - if (num_base_interface_layers) + if (num_base_interface_layers_top || num_base_interface_layers_bottom) base_interface_layers.assign(intermediate_layers.size(), nullptr); tbb::spin_mutex layer_storage_mutex; // Insert a new layer into base_interface_layers, if intersection with base exists. @@ -2754,7 +2760,8 @@ std::pair(0, int(intermediate_layers.size())), - [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, num_interface_layers, num_base_interface_layers, num_interface_layers_only, + [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, + num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below // this intermediate layer. @@ -2765,45 +2772,51 @@ std::pairprint_z; - coordf_t top_inteface_z = std::numeric_limits::max(); - coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers + 1)]->bottom_z; - coordf_t bottom_interface_z = - std::numeric_limits::max(); - if (num_base_interface_layers > 0) { - // Some base interface layers will be generated. - if (num_interface_layers_only == 0) - // Only base interface layers to generate. - std::swap(top_inteface_z, bottom_interface_z); - else { - top_inteface_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only - 1)]->print_z; - bottom_interface_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only)]->bottom_z; - } - } - // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON - // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_top_contact_projected_interface; Polygons polygons_top_contact_projected_base; - for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { - const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; - //FIXME maybe this adds one interface layer in excess? - if (top_contact_layer.bottom_z - EPSILON > top_z) - break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); - } - // Move idx_bottom_contact_first up until touching bottom_z. - idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); - // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_bottom_contact_projected_interface; Polygons polygons_bottom_contact_projected_base; - for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { - const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; - if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) - break; - polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); + if (num_interface_layers_top > -1) { + // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; + coordf_t top_inteface_z = std::numeric_limits::max(); + if (num_base_interface_layers_top > 0) + // Some top base interface layers will be generated. + top_inteface_z = num_interface_layers_only_top == 0 ? + // Only base interface layers to generate. + - std::numeric_limits::max() : + intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; + // Move idx_top_contact_first up until above the current print_z. + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { + const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; + //FIXME maybe this adds one interface layer in excess? + if (top_contact_layer.bottom_z - EPSILON > top_z) + break; + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); + } + } + if (num_interface_layers_bottom > -1) { + // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; + coordf_t bottom_interface_z = - std::numeric_limits::max(); + if (num_base_interface_layers_bottom > 0) + // Some bottom base interface layers will be generated. + bottom_interface_z = num_interface_layers_only_bottom == 0 ? + // Only base interface layers to generate. + std::numeric_limits::max() : + intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; + // Move idx_bottom_contact_first up until touching bottom_z. + idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { + const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; + if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) + break; + polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); + } } - MyLayer *interface_layer = nullptr; if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { interface_layer = insert_layer( @@ -3712,10 +3725,6 @@ void PrintObjectSupportMaterial::generate_toolpaths( base_layer.merge(std::move(top_contact_layer)); else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging) std::swap(base_layer, top_contact_layer); - if (base_layer.could_merge(bottom_contact_layer)) - base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging) - std::swap(base_layer, bottom_contact_layer); } } else { loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow); @@ -3725,6 +3734,12 @@ void PrintObjectSupportMaterial::generate_toolpaths( if (top_contact_layer.could_merge(interface_layer)) top_contact_layer.merge(std::move(interface_layer)); } + if ((m_object_config->support_material_interface_layers == 0 || m_object_config->support_material_bottom_interface_layers == 0) && m_can_merge_support_regions) { + if (base_layer.could_merge(bottom_contact_layer)) + base_layer.merge(std::move(bottom_contact_layer)); + else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging) + std::swap(base_layer, bottom_contact_layer); + } #if 0 if ( ! interface_layer.empty() && ! base_layer.empty()) { diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 01ec72837..80ebe6ea8 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -286,7 +286,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field(el, have_support_material); toggle_field("support_material_threshold", have_support_material_auto); - for (auto el : { "support_material_interface_spacing", "support_material_interface_extruder", + for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", "support_material_interface_speed", "support_material_interface_contact_loops" }) toggle_field(el, have_support_material && have_support_interface); toggle_field("support_material_synchronize_layers", have_support_soluble); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 645d31f4b..472db4976 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1512,6 +1512,7 @@ void TabPrint::build() optgroup->append_single_option_line("support_material_spacing", category_path + "pattern-spacing-0-inf"); optgroup->append_single_option_line("support_material_angle", category_path + "pattern-angle"); optgroup->append_single_option_line("support_material_interface_layers", category_path + "interface-layers"); + optgroup->append_single_option_line("support_material_bottom_interface_layers", category_path + "interface-layers"); optgroup->append_single_option_line("support_material_interface_pattern", category_path + "interface-pattern"); optgroup->append_single_option_line("support_material_interface_spacing", category_path + "interface-pattern-spacing"); optgroup->append_single_option_line("support_material_interface_contact_loops", category_path + "interface-loops"); From f3f10ff002ba4177c84295b744eb79d4a42f8643 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 10 Mar 2021 09:00:54 +0100 Subject: [PATCH 45/60] Follow up on https://github.com/prusa3d/PrusaSlicer/pull/5219#issuecomment-794515454 Initializing random generator in PlacholderParser from system clock. --- src/libslic3r/GCode.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 28a68e171..aef6222a3 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -1129,6 +1130,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser.update_timestamp(); + m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count()); print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); // Get optimal tool ordering to minimize tool switches of a multi-exruder print. From 73b88e6ce0fa21a031490f148751114af73d573c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 10 Mar 2021 12:14:45 +0100 Subject: [PATCH 46/60] Splitting FDM support gap to top / bottom, introducing support_material_bottom_contact_distance Fixing Crash in support generation after fcb714c (repro attached) #6195 --- src/libslic3r/Preset.cpp | 3 +- src/libslic3r/Print.cpp | 3 +- src/libslic3r/PrintConfig.cpp | 37 ++++++- src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/PrintObject.cpp | 15 +-- src/libslic3r/Slicing.cpp | 4 +- src/libslic3r/Slicing.hpp | 46 ++++---- src/libslic3r/SupportMaterial.cpp | 147 ++++++++++++-------------- src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/Plater.cpp | 3 +- src/slic3r/GUI/Tab.cpp | 1 + 11 files changed, 144 insertions(+), 118 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 412dbc678..7dac34342 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -429,7 +429,8 @@ const std::vector& Preset::print_options() "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", - "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", + "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", + "support_material_contact_distance", "support_material_bottom_contact_distance", "support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 79c4e72a7..e39fd8685 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1326,7 +1326,8 @@ std::string Print::validate(std::string* warning) const return L("The Wipe Tower is only supported for multiple objects if they have equal layer heights"); if (slicing_params.raft_layers() != slicing_params0.raft_layers()) return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers"); - if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance) + if (slicing_params0.gap_object_support != slicing_params.gap_object_support || + slicing_params0.gap_support_object != slicing_params.gap_support_object) return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance"); if (! equal_layering(slicing_params, slicing_params0)) return L("The Wipe Tower is only supported for multiple objects if they are sliced equally."); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d744cb2a7..5fa4dfb01 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2235,7 +2235,7 @@ void PrintConfigDef::init_fff_params() def = this->add("support_material_contact_distance", coFloat); def->gui_type = "f_enum_open"; - def->label = L("Contact Z distance"); + def->label = L("Top contact Z distance"); def->category = L("Support material"); def->tooltip = L("The vertical distance between object and support material interface. " "Setting this to 0 will also prevent Slic3r from using bridge flow and speed " @@ -2243,12 +2243,31 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); // def->min = 0; def->enum_values.push_back("0"); + def->enum_values.push_back("0.1"); def->enum_values.push_back("0.2"); def->enum_labels.push_back(L("0 (soluble)")); + def->enum_labels.push_back(L("0.1 (detachable)")); def->enum_labels.push_back(L("0.2 (detachable)")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); + def = this->add("support_material_bottom_contact_distance", coFloat); + def->gui_type = "f_enum_open"; + def->label = L("Bottom contact Z distance"); + def->category = L("Support material"); + def->tooltip = L("The vertical distance between the object top surface and the support material interface. " + "If set to zero, support_material_contact_distance will be used for both top and bottom contact Z distances."); + def->sidetext = L("mm"); +// def->min = 0; + def->enum_values.push_back("0"); + def->enum_values.push_back("0.1"); + def->enum_values.push_back("0.2"); + def->enum_labels.push_back(L("same as top")); + def->enum_labels.push_back(L("0.1")); + def->enum_labels.push_back(L("0.2")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("support_material_enforce_layers", coInt); def->label = L("Enforce support for the first"); def->category = L("Support material"); @@ -2298,22 +2317,36 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(1)); - def = this->add("support_material_interface_layers", coInt); + auto support_material_interface_layers = def = this->add("support_material_interface_layers", coInt); + def->gui_type = "f_enum_open"; def->label = L("Top interface layers"); def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material."); def->sidetext = L("layers"); def->min = 0; + def->enum_values.push_back("0"); + def->enum_values.push_back("1"); + def->enum_values.push_back("2"); + def->enum_values.push_back("3"); + def->enum_labels.push_back(L("0 (off)")); + def->enum_labels.push_back(L("1 (light)")); + def->enum_labels.push_back(L("2 (default)")); + def->enum_labels.push_back(L("3 (heavy)")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(3)); def = this->add("support_material_bottom_interface_layers", coInt); + def->gui_type = "f_enum_open"; def->label = L("Bottom interface layers"); def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material. " "Set to -1 to use support_material_interface_layers"); def->sidetext = L("layers"); def->min = -1; + def->enum_values.push_back("-1"); + append(def->enum_values, support_material_interface_layers->enum_values); + def->enum_labels.push_back(L("same as top")); + append(def->enum_labels, support_material_interface_layers->enum_labels); def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(-1)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 4a4647b73..abeb29d4b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -503,6 +503,7 @@ public: ConfigOptionFloat support_material_angle; ConfigOptionBool support_material_buildplate_only; ConfigOptionFloat support_material_contact_distance; + ConfigOptionFloat support_material_bottom_contact_distance; ConfigOptionInt support_material_enforce_layers; ConfigOptionInt support_material_extruder; ConfigOptionFloatOrPercent support_material_extrusion_width; @@ -555,6 +556,7 @@ protected: OPT_PTR(support_material_angle); OPT_PTR(support_material_buildplate_only); OPT_PTR(support_material_contact_distance); + OPT_PTR(support_material_bottom_contact_distance); OPT_PTR(support_material_enforce_layers); OPT_PTR(support_material_interface_contact_loops); OPT_PTR(support_material_extruder); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index a3b0fb787..e86eb2c9c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -546,15 +546,9 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "extra_perimeters" || opt_key == "gap_fill_enabled" || opt_key == "gap_fill_speed" - || opt_key == "overhangs" || opt_key == "first_layer_extrusion_width" - || opt_key == "fuzzy_skin" - || opt_key == "fuzzy_skin_thickness" - || opt_key == "fuzzy_skin_point_dist" || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" - || opt_key == "thin_walls" - || opt_key == "thick_bridges" || opt_key == "external_perimeters_first") { steps.emplace_back(posPerimeters); } else if ( @@ -586,6 +580,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_material_enforce_layers" || opt_key == "support_material_extruder" || opt_key == "support_material_extrusion_width" + || opt_key == "support_material_bottom_contact_distance" || opt_key == "support_material_interface_layers" || opt_key == "support_material_bottom_interface_layers" || opt_key == "support_material_interface_pattern" @@ -654,7 +649,13 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posPrepareInfill); } else if ( opt_key == "external_perimeter_extrusion_width" - || opt_key == "perimeter_extruder") { + || opt_key == "perimeter_extruder" + || opt_key == "fuzzy_skin" + || opt_key == "fuzzy_skin_thickness" + || opt_key == "fuzzy_skin_point_dist" + || opt_key == "overhangs" + || opt_key == "thin_walls" + || opt_key == "thick_bridges") { steps.emplace_back(posPerimeters); steps.emplace_back(posSupportMaterial); } else if (opt_key == "bridge_flow_ratio") { diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 083b41202..d0b1e9ce2 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -112,8 +112,10 @@ SlicingParameters SlicingParameters::create_from_config( if (! soluble_interface) { params.gap_raft_object = object_config.raft_contact_distance.value; - params.gap_object_support = object_config.support_material_contact_distance.value; + params.gap_object_support = object_config.support_material_bottom_contact_distance.value; params.gap_support_object = object_config.support_material_contact_distance.value; + if (params.gap_object_support <= 0) + params.gap_object_support = params.gap_support_object; } if (params.base_raft_layers > 0) { diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 8b3d5c917..489b2768f 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -26,7 +26,7 @@ class DynamicPrintConfig; // (using a normal flow over a soluble support, using a bridging flow over a non-soluble support). struct SlicingParameters { - SlicingParameters() { memset(this, 0, sizeof(SlicingParameters)); } + SlicingParameters() = default; static SlicingParameters create_from_config( const PrintConfig &print_config, @@ -44,58 +44,58 @@ struct SlicingParameters // Height of the object to be printed. This value does not contain the raft height. coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; } - bool valid; + bool valid { false }; // Number of raft layers. - size_t base_raft_layers; + size_t base_raft_layers { 0 }; // Number of interface layers including the contact layer. - size_t interface_raft_layers; + size_t interface_raft_layers { 0 }; // Layer heights of the raft (base, interface and a contact layer). - coordf_t base_raft_layer_height; - coordf_t interface_raft_layer_height; - coordf_t contact_raft_layer_height; + coordf_t base_raft_layer_height { 0 }; + coordf_t interface_raft_layer_height { 0 }; + coordf_t contact_raft_layer_height { 0 }; // The regular layer height, applied for all but the first layer, if not overridden by layer ranges // or by the variable layer thickness table. - coordf_t layer_height; + coordf_t layer_height { 0 }; // Minimum / maximum layer height, to be used for the automatic adaptive layer height algorithm, // or by an interactive layer height editor. - coordf_t min_layer_height; - coordf_t max_layer_height; - coordf_t max_suport_layer_height; + coordf_t min_layer_height { 0 }; + coordf_t max_layer_height { 0 }; + coordf_t max_suport_layer_height { 0 }; // First layer height of the print, this may be used for the first layer of the raft // or for the first layer of the print. - coordf_t first_print_layer_height; + coordf_t first_print_layer_height { 0 }; // Thickness of the first layer. This is either the first print layer thickness if printed without a raft, // or a bridging flow thickness if printed over a non-soluble raft, // or a normal layer height if printed over a soluble raft. - coordf_t first_object_layer_height; + coordf_t first_object_layer_height { 0 }; // If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow. - bool first_object_layer_bridging; + bool first_object_layer_bridging { false }; // Soluble interface? (PLA soluble in water, HIPS soluble in lemonen) // otherwise the interface must be broken off. - bool soluble_interface; + bool soluble_interface { false }; // Gap when placing object over raft. - coordf_t gap_raft_object; + coordf_t gap_raft_object { 0 }; // Gap when placing support over object. - coordf_t gap_object_support; + coordf_t gap_object_support { 0 }; // Gap when placing object over support. - coordf_t gap_support_object; + coordf_t gap_support_object { 0 }; // Bottom and top of the printed object. // If printed without a raft, object_print_z_min = 0 and object_print_z_max = object height. // Otherwise object_print_z_min is equal to the raft height. - coordf_t raft_base_top_z; - coordf_t raft_interface_top_z; - coordf_t raft_contact_top_z; + coordf_t raft_base_top_z { 0 }; + coordf_t raft_interface_top_z { 0 }; + coordf_t raft_contact_top_z { 0 }; // In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer. - coordf_t object_print_z_min; - coordf_t object_print_z_max; + coordf_t object_print_z_min { 0 }; + coordf_t object_print_z_max { 0 }; }; static_assert(IsTriviallyCopyable::value, "SlicingParameters class is not POD (and it should be - see constructor)."); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 7747559e6..8dc939aed 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -397,14 +398,6 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr dst.insert(dst.end(), src.begin(), src.end()); } -// Compare layers lexicographically. -struct MyLayersPtrCompare -{ - bool operator()(const PrintObjectSupportMaterial::MyLayer* layer1, const PrintObjectSupportMaterial::MyLayer* layer2) const { - return *layer1 < *layer2; - } -}; - void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; @@ -467,10 +460,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage); -// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); - this->trim_support_layers_by_object(object, top_contacts, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); #ifdef SLIC3R_DEBUG for (const MyLayer *layer : top_contacts) @@ -552,7 +542,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) layers_append(layers_sorted, interface_layers); layers_append(layers_sorted, base_interface_layers); // Sort the layers lexicographically by a raising print_z and a decreasing height. - std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); + std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); int layer_id = 0; assert(object.support_layers().empty()); for (size_t i = 0; i < layers_sorted.size();) { @@ -1585,11 +1575,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } else if (m_slicing_params.soluble_interface) { // Align the contact surface height with a layer immediately below the supported layer. // Interface layer will be synchronized with the object. - new_layer.print_z = layer.print_z - layer.height; + new_layer.print_z = layer.bottom_z(); new_layer.height = object.layers()[layer_id - 1]->height; new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z; } else { - new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance; + new_layer.print_z = layer.bottom_z() - m_slicing_params.gap_object_support; new_layer.bottom_z = new_layer.print_z; new_layer.height = 0.; // Ignore this contact area if it's too low. @@ -1616,7 +1606,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ for (const LayerRegion *region : layer.regions()) bridging_height += region->region()->bridging_height_avg(*m_print_config); bridging_height /= coordf_t(layer.regions().size()); - coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance; + coordf_t bridging_print_z = layer.print_z - bridging_height - m_slicing_params.gap_support_object; if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) { // Not below the first layer height means this layer is printable. if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { @@ -1892,7 +1882,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // Place a bridge flow interface layer or the normal flow interface layer over the top surface. m_support_material_bottom_interface_flow.height(); layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z : - layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; + layer.print_z + layer_new.height + m_slicing_params.gap_object_support; layer_new.bottom_z = layer.print_z; layer_new.idx_object_layer_below = layer_id; layer_new.bridging = ! m_slicing_params.soluble_interface && m_object_config->thick_bridges; @@ -2039,11 +2029,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta task_group.wait(); } std::reverse(bottom_contacts.begin(), bottom_contacts.end()); -// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy); - trim_support_layers_by_object(object, bottom_contacts, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); - + trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); } // ! top_contacts.empty() return bottom_contacts; @@ -2473,10 +2459,7 @@ void PrintObjectSupportMaterial::generate_base_layers( ++ iRun; #endif /* SLIC3R_DEBUG */ -// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy); - this->trim_support_layers_by_object(object, intermediate_layers, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, - m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); } void PrintObjectSupportMaterial::trim_support_layers_by_object( @@ -2510,7 +2493,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( // BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size(); assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON); // Find the overlapping object layers including the extra above / below gap. - coordf_t z_threshold = support_layer.print_z - support_layer.height - gap_extra_below + EPSILON; + coordf_t z_threshold = support_layer.bottom_print_z() - gap_extra_below + EPSILON; idx_object_layer_overlapping = idx_higher_or_equal( object.layers().begin(), object.layers().end(), idx_object_layer_overlapping, [z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; }); @@ -2519,7 +2502,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( size_t i = idx_object_layer_overlapping; for (; i < object.layers().size(); ++ i) { const Layer &object_layer = *object.layers()[i]; - if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) + if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON) break; polygons_append(polygons_trimming, offset(object_layer.lslices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } @@ -2537,6 +2520,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (region->region()->config().overhangs.value) + // Add bridging perimeters. SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); } if (! some_region_overlaps) @@ -2896,7 +2880,8 @@ static inline void fill_expolygons_with_sheath_generate_paths( float density, ExtrusionRole role, const Flow &flow, - bool with_sheath) + bool with_sheath, + bool no_sort) { if (polygons.empty()) return; @@ -2916,8 +2901,12 @@ static inline void fill_expolygons_with_sheath_generate_paths( for (ExPolygon &expoly : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) { // Don't reorder the skirt and its infills. - auto eec = std::make_unique(); - eec->no_sort = true; + std::unique_ptr eec; + if (no_sort) { + eec = std::make_unique(); + eec->no_sort = true; + } + ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; // Draw the perimeters. Polylines polylines; polylines.reserve(expoly.holes.size() + 1); @@ -2927,10 +2916,11 @@ static inline void fill_expolygons_with_sheath_generate_paths( pl.clip_end(clip_length); polylines.emplace_back(std::move(pl)); } - extrusion_entities_append_paths(eec->entities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); + extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); // Fill in the rest. - fill_expolygons_generate_paths(eec->entities, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); - dst.emplace_back(eec.release()); + fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); + if (no_sort) + dst.emplace_back(eec.release()); } } @@ -3255,6 +3245,9 @@ static std::string dbg_index_to_color(int idx) // Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, // leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers // to stick too firmly to the object. +// +// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer +// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. void modulate_extrusion_by_overlapping_layers( // Extrusions generated for this_layer. ExtrusionEntitiesPtr &extrusions_in_out, @@ -3343,8 +3336,8 @@ void modulate_extrusion_by_overlapping_layers( // Collect the paths of this_layer. { Polylines &polylines = path_fragments.back().polylines; - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); + for (ExtrusionEntity *ee : extrusions_in_out) { + ExtrusionPath *path = dynamic_cast(ee); assert(path != nullptr); polylines.emplace_back(Polyline(std::move(path->polyline))); path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); @@ -3504,7 +3497,6 @@ void PrintObjectSupportMaterial::generate_toolpaths( const MyLayersPtr &interface_layers, const MyLayersPtr &base_interface_layers) const { -// Slic3r::debugf "Generating patterns\n"; // loop_interface_processor with a given circle radius. LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width()); loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; @@ -3603,7 +3595,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler, float(support_density), // Extrusion parameters erSupportMaterial, flow, - with_sheath); + with_sheath, false); } } @@ -3636,7 +3628,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Extrusion parameters (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow, // sheath at first layer - support_layer_id == 0); + support_layer_id == 0, support_layer_id == 0); } }); @@ -3646,12 +3638,20 @@ void PrintObjectSupportMaterial::generate_toolpaths( std::vector overlapping; }; struct LayerCache { - MyLayerExtruded bottom_contact_layer; - MyLayerExtruded top_contact_layer; - MyLayerExtruded base_layer; - MyLayerExtruded interface_layer; - MyLayerExtruded base_interface_layer; - std::vector overlaps; + MyLayerExtruded bottom_contact_layer; + MyLayerExtruded top_contact_layer; + MyLayerExtruded base_layer; + MyLayerExtruded interface_layer; + MyLayerExtruded base_interface_layer; + boost::container::static_vector nonempty; + + void add_nonempty_and_sort() { + for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) + if (! item->empty()) + this->nonempty.emplace_back(item); + // Sort the layers with the same print_z coordinate by their heights, thickest first. + std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); + } }; std::vector layer_caches(support_layers.size(), LayerCache()); @@ -3816,6 +3816,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); float density = float(support_density); bool sheath = with_sheath; + bool no_sort = false; if (base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). filler = filler_first_layer; @@ -3827,7 +3828,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( //FIXME When paralellizing, each thread shall have its own copy of the fillers. filler->spacing = flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - sheath = true; + sheath = true; + no_sort = true; } fill_expolygons_with_sheath_generate_paths( // Destination @@ -3838,7 +3840,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler, density, // Extrusion parameters erSupportMaterial, flow, - sheath); + sheath, no_sort); } @@ -3847,24 +3849,13 @@ void PrintObjectSupportMaterial::generate_toolpaths( base_layer.could_merge(base_interface_layer)) base_layer.merge(std::move(base_interface_layer)); - layer_cache.overlaps.reserve(5); - if (! bottom_contact_layer.empty()) - layer_cache.overlaps.push_back(&bottom_contact_layer); - if (! top_contact_layer.empty()) - layer_cache.overlaps.push_back(&top_contact_layer); - if (! interface_layer.empty()) - layer_cache.overlaps.push_back(&interface_layer); - if (! base_interface_layer.empty()) - layer_cache.overlaps.push_back(&base_interface_layer); - if (! base_layer.empty()) - layer_cache.overlaps.push_back(&base_layer); - // Sort the layers with the same print_z coordinate by their heights, thickest first. - std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); + layer_cache.add_nonempty_and_sort(); + // Collect the support areas with this print_z into islands, as there is no need // for retraction over these islands. Polygons polys; // Collect the extrusions, sorted by the bottom extrusion height. - for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) { + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { // Collect islands to polys. layer_cache_item.layer_extruded->polygons_append(polys); // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" @@ -3878,32 +3869,22 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Collect overlapping top/bottom surfaces. layer_cache_item.overlapping.reserve(20); coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; - for (int i = int(idx_layer_bottom_contact) - 1; i >= 0 && bottom_contacts[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(bottom_contacts[i]); - for (int i = int(idx_layer_top_contact) - 1; i >= 0 && top_contacts[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(top_contacts[i]); + auto add_overlapping = [&layer_cache_item, bottom_z](const MyLayersPtr &layers, size_t idx_top) { + for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) + layer_cache_item.overlapping.push_back(layers[i]); + }; + add_overlapping(top_contacts, idx_layer_top_contact); if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) { // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. - for (int i = int(idx_layer_intermediate) - 1; i >= 0 && intermediate_layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(intermediate_layers[i]); - for (int i = int(idx_layer_interface) - 1; i >= 0 && interface_layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(interface_layers[i]); - for (int i = int(idx_layer_base_interface) - 1; i >= 0 && base_interface_layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(base_interface_layers[i]); + add_overlapping(intermediate_layers, idx_layer_intermediate); + add_overlapping(interface_layers, idx_layer_interface); + add_overlapping(base_interface_layers, idx_layer_base_interface); } - std::sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), MyLayersPtrCompare()); + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); } if (! polys.empty()) expolygons_append(support_layer.support_islands.expolygons, union_ex(polys)); - /* { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("islands_" . $z . ".svg", - red_expolygons => union_ex($contact), - green_expolygons => union_ex($interface), - green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ], - polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ], - ); - } */ } // for each support_layer_id }); @@ -3914,7 +3895,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) { + // For all extrusion types at this print_z, ordered by decreasing layer height: + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Trim the extrusion height from the bottom by the overlapping layers. modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 80ebe6ea8..6e0f13528 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -285,6 +285,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) "support_material_xy_spacing" }) toggle_field(el, have_support_material); toggle_field("support_material_threshold", have_support_material_auto); + toggle_field("support_material_bottom_contact_distance", have_support_material && ! have_support_soluble); for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", "support_material_interface_speed", "support_material_interface_contact_loops" }) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c6b3ed89f..65b4b7b28 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1762,7 +1762,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "first_layer_height", "min_layer_height", "max_layer_height", "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers", - "support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers" + "support_material", "support_material_extruder", "support_material_interface_extruder", + "support_material_contact_distance", "support_material_bottom_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) , m_ui_jobs(this) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 472db4976..7b3f23eb4 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1507,6 +1507,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Options for support material and raft")); optgroup->append_single_option_line("support_material_contact_distance", category_path + "contact-z-distance"); + optgroup->append_single_option_line("support_material_bottom_contact_distance", category_path + "contact-z-distance"); optgroup->append_single_option_line("support_material_pattern", category_path + "pattern"); optgroup->append_single_option_line("support_material_with_sheath", category_path + "with-sheath-around-the-support"); optgroup->append_single_option_line("support_material_spacing", category_path + "pattern-spacing-0-inf"); From 2494a8f384ff12f40e3423722f74d3864fe2ed57 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 10 Mar 2021 16:16:00 +0100 Subject: [PATCH 47/60] Allowing ints with open enums in combo boxes. --- src/libslic3r/PrintConfig.cpp | 4 ++-- src/slic3r/GUI/Field.cpp | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5fa4dfb01..3aad02f91 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2318,7 +2318,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(1)); auto support_material_interface_layers = def = this->add("support_material_interface_layers", coInt); - def->gui_type = "f_enum_open"; + def->gui_type = "i_enum_open"; def->label = L("Top interface layers"); def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material."); @@ -2336,7 +2336,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(3)); def = this->add("support_material_bottom_interface_layers", coInt); - def->gui_type = "f_enum_open"; + def->gui_type = "i_enum_open"; def->label = L("Bottom interface layers"); def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material. " diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7303f4d72..02ef87775 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -965,15 +965,27 @@ void Choice::BUILD() { } if (is_defined_input_value(window, m_opt.type)) { - if (m_opt.type == coFloatOrPercent) { + switch (m_opt.type) { + case coFloatOrPercent: + { std::string old_val = !m_value.empty() ? boost::any_cast(m_value) : ""; if (old_val == boost::any_cast(get_value())) return; + break; } - else { - double old_val = !m_value.empty() ? boost::any_cast(m_value) : -99999; - if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001) + case coInt: + { + int old_val = !m_value.empty() ? boost::any_cast(m_value) : 0; + if (old_val == boost::any_cast(get_value())) return; + break; + } + default: + { + double old_val = !m_value.empty() ? boost::any_cast(m_value) : -99999; + if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001) + return; + } } on_change_field(); } From 9c80c6a4af809c009c98225d8a79472b4dad73d2 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 11 Mar 2021 11:48:27 +0100 Subject: [PATCH 48/60] Disable multi-sampling with virgl (VirtualGL) on Linux. Namely, on ChromeOS virgl flips red/blue channels at least on some computers with multi-sampling enabled. It seems it is sufficient to disable multi-sampling after the OpenGL context is created. --- src/slic3r/GUI/OpenGLManager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 0fa97bfc1..693f544e7 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -256,6 +256,11 @@ bool OpenGLManager::init_gl() } if (valid_version) { + if (s_gl_info.get_renderer() == "virgl") + // Disable multi-sampling with virgl (VirtualGL) on Linux. + // Namely, on ChromeOS virgl flips red/blue channels at least on some computers with multi-sampling enabled. + // It seems it is sufficient to disable multi-sampling after the OpenGL context is created. + s_multisample = EMultisampleState::Disabled; // load shaders auto [result, error] = m_shaders_manager.init(); if (!result) { From 2b1970872078166f24289342594a16d5c0f8bac8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 11 Mar 2021 12:22:06 +0100 Subject: [PATCH 49/60] Further ChromeOS support: Detect removable media mounted through ChromeOS --- src/slic3r/GUI/OpenGLManager.cpp | 2 ++ src/slic3r/GUI/RemovableDriveManager.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 693f544e7..9a1eecc11 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -256,11 +256,13 @@ bool OpenGLManager::init_gl() } if (valid_version) { +#ifdef __linux__ if (s_gl_info.get_renderer() == "virgl") // Disable multi-sampling with virgl (VirtualGL) on Linux. // Namely, on ChromeOS virgl flips red/blue channels at least on some computers with multi-sampling enabled. // It seems it is sufficient to disable multi-sampling after the OpenGL context is created. s_multisample = EMultisampleState::Disabled; +#endif // __linux__ // load shaders auto [result, error] = m_shaders_manager.init(); if (!result) { diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index a1247f3a0..85d91382a 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -247,6 +247,9 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons path = "/run" + path; pp = "/run"+pp; search_for_drives_internal::search_path(path, pp, current_drives); + + // ChromeOS specific: search /mnt/chromeos/removable/* folder + search_for_drives_internal::search_path("/mnt/chromeos/removable/*", "/mnt/chromeos/removable", current_drives); #endif return current_drives; From ca8cf0a9f12b8f2046ddd1cfdd0a06f45cb05f5e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 11 Mar 2021 13:25:13 +0100 Subject: [PATCH 50/60] Detecting platform, namely reading /proc/version to detect Chromium OS. Disabling Eject on Chromium, detecting removable media the Chromium way. --- src/slic3r/CMakeLists.txt | 2 ++ src/slic3r/GUI/GUI_Init.cpp | 3 ++ src/slic3r/GUI/OpenGLManager.cpp | 16 +++++----- src/slic3r/GUI/Plater.cpp | 5 ++- src/slic3r/GUI/RemovableDriveManager.cpp | 39 ++++++++++++++---------- 5 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 505c8f0f2..40d74159d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -207,6 +207,8 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp + Utils/Platform.cpp + Utils/Platform.hpp Utils/Process.cpp Utils/Process.hpp Utils/Profile.hpp diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 839782741..3c77f3335 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/Utils/Platform.hpp" // To show a message box if GUI initialization ends up with an exception thrown. #include @@ -36,6 +37,8 @@ int GUI_Run(GUI_InitParams ¶ms) signal(SIGCHLD, SIG_DFL); #endif // __APPLE__ + detect_platform(); + try { GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) { diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 9a1eecc11..8e8774036 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -4,6 +4,7 @@ #include "GUI.hpp" #include "I18N.hpp" #include "3DScene.hpp" +#include "slic3r/Utils/Platform.hpp" #include @@ -256,13 +257,6 @@ bool OpenGLManager::init_gl() } if (valid_version) { -#ifdef __linux__ - if (s_gl_info.get_renderer() == "virgl") - // Disable multi-sampling with virgl (VirtualGL) on Linux. - // Namely, on ChromeOS virgl flips red/blue channels at least on some computers with multi-sampling enabled. - // It seems it is sufficient to disable multi-sampling after the OpenGL context is created. - s_multisample = EMultisampleState::Disabled; -#endif // __linux__ // load shaders auto [result, error] = m_shaders_manager.init(); if (!result) { @@ -326,7 +320,13 @@ void OpenGLManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; bool enable_multisample = wxVersion >= 30003; - s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? EMultisampleState::Enabled : EMultisampleState::Disabled; + s_multisample = + enable_multisample && + // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, + // at least on some platforms. + (platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) && + wxGLCanvas::IsDisplaySupported(attribList) + ? EMultisampleState::Enabled : EMultisampleState::Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample"); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 65b4b7b28..f131c244f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -75,6 +75,7 @@ #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "../Utils/PresetUpdater.hpp" +#include "../Utils/Platform.hpp" #include "../Utils/Process.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" @@ -3681,7 +3682,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) // If writing to removable drive was scheduled, show notification with eject button if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { show_action_buttons(false); - notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, true); + notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, + // Don't offer the "Eject" button on ChromeOS, the Linux side has no control over it. + platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium); wxGetApp().removable_drive_manager()->set_exporting_finished(true); }else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error) notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false); diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index 85d91382a..c200956e1 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -1,4 +1,5 @@ #include "RemovableDriveManager.hpp" +#include "slic3r/Utils/Platform.hpp" #include #include @@ -231,25 +232,28 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons #else - //search /media/* folder - search_for_drives_internal::search_path("/media/*", "/media", current_drives); + if (platform() == Platform::Linux && platform_flavor() == PlatformFlavor::LinuxOnChromium) { + // ChromeOS specific: search /mnt/chromeos/removable/* folder + search_for_drives_internal::search_path("/mnt/chromeos/removable/*", "/mnt/chromeos/removable", current_drives); + } else { + //search /media/* folder + search_for_drives_internal::search_path("/media/*", "/media", current_drives); - //search_path("/Volumes/*", "/Volumes"); - std::string path(std::getenv("USER")); - std::string pp(path); + //search_path("/Volumes/*", "/Volumes"); + std::string path(std::getenv("USER")); + std::string pp(path); - //search /media/USERNAME/* folder - pp = "/media/"+pp; - path = "/media/" + path + "/*"; - search_for_drives_internal::search_path(path, pp, current_drives); + //search /media/USERNAME/* folder + pp = "/media/"+pp; + path = "/media/" + path + "/*"; + search_for_drives_internal::search_path(path, pp, current_drives); - //search /run/media/USERNAME/* folder - path = "/run" + path; - pp = "/run"+pp; - search_for_drives_internal::search_path(path, pp, current_drives); + //search /run/media/USERNAME/* folder + path = "/run" + path; + pp = "/run"+pp; + search_for_drives_internal::search_path(path, pp, current_drives); + } - // ChromeOS specific: search /mnt/chromeos/removable/* folder - search_for_drives_internal::search_path("/mnt/chromeos/removable/*", "/mnt/chromeos/removable", current_drives); #endif return current_drives; @@ -446,7 +450,10 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() RemovableDriveManager::RemovableDrivesStatus out; { tbb::mutex::scoped_lock lock(m_drives_mutex); - out.has_eject = this->find_last_save_path_drive_data() != m_current_drives.end(); + out.has_eject = + // Cannot control eject on Chromium. + (platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) && + this->find_last_save_path_drive_data() != m_current_drives.end(); out.has_removable_drives = ! m_current_drives.empty(); } if (! out.has_eject) From 051ba0e6f4fce9456e6935086285a1cd718b7078 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 11 Mar 2021 13:45:45 +0100 Subject: [PATCH 51/60] New files missing from the previous commit. --- src/slic3r/Utils/Platform.cpp | 59 +++++++++++++++++++++++++++++++++++ src/slic3r/Utils/Platform.hpp | 40 ++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/slic3r/Utils/Platform.cpp create mode 100644 src/slic3r/Utils/Platform.hpp diff --git a/src/slic3r/Utils/Platform.cpp b/src/slic3r/Utils/Platform.cpp new file mode 100644 index 000000000..d30f30fc8 --- /dev/null +++ b/src/slic3r/Utils/Platform.cpp @@ -0,0 +1,59 @@ +#include "Platform.hpp" + + +// For starting another PrusaSlicer instance on OSX. +// Fails to compile on Windows on the build server. + +#include + +namespace Slic3r { +namespace GUI { + +static auto s_platform = Platform::Uninitialized; +static auto s_platform_flavor = PlatformFlavor::Uninitialized; + +void detect_platform() +{ +#if defined(_WIN32) + s_platform = Platform::Windows; + s_platform_flavor = PlatformFlavor::Generic; +#elif defined(__APPLE__) + s_platform = Platform::OSX; + s_platform_flavor = PlatformFlavor::Generic; +#elif defined(__linux__) + s_platform = Platform::Linux; + s_platform_flavor = PlatformFlavor::GenericLinux; + // Test for Chromium. + { + FILE *f = ::fopen("/proc/version", "rt"); + if (f) { + char buf[4096]; + // Read the 1st line. + if (::fgets(buf, 4096, f) && strstr(buf, "Chromium OS") != nullptr) + s_platform_flavor = LinuxOnChromium; + ::fclose(f); + } + } +#elif defined(__OpenBSD__) + s_platform = Platform::BSD; + s_platform_flavor = PlatformFlavor::OpenBSD; +#else + // This should not happen. + static_assert(false, "Unknown platform detected"); + s_platform = Platform::Unknown; + s_platform_flavor = PlatformFlavor::Unknown; +#endif +} + +Platform platform() +{ + return s_platform; +} + +PlatformFlavor platform_flavor() +{ + return s_platform_flavor; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/Utils/Platform.hpp b/src/slic3r/Utils/Platform.hpp new file mode 100644 index 000000000..e11bde412 --- /dev/null +++ b/src/slic3r/Utils/Platform.hpp @@ -0,0 +1,40 @@ +#ifndef SLIC3R_GUI_Utils_Platform_HPP +#define SLIC3R_GUI_Utils_Platform_HPP + +namespace Slic3r { +namespace GUI { + +enum class Platform +{ + Uninitialized, + Unknown, + Windows, + OSX, + Linux, + BSD, +}; + +enum class PlatformFlavor +{ + Uninitialized, + Unknown, + // For Windows and OSX, until we need to be more specific. + Generic, + // For Platform::Linux + GenericLinux, + LinuxOnChromium, + // For Platform::BSD + OpenBSD, +}; + +// To be called on program start-up. +void detect_platform(); + +Platform platform(); +PlatformFlavor platform_flavor(); + + +} // namespace GUI +} // namespace Slic3r + +#endif // SLIC3R_GUI_Utils_Platform_HPP From a9c3d270e6384098cb26c6263093e6ead1109a29 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 11 Mar 2021 15:01:24 +0100 Subject: [PATCH 52/60] ConfigOptions: GUI type as enum, not string. Fixing compilation error in the new Platform code. Fixing one issue in FDM support after splitting the top/bottom interface layers. --- src/libslic3r/Config.hpp | 20 +++++++++++++++++++- src/libslic3r/PrintConfig.cpp | 30 +++++++++++++++--------------- src/libslic3r/SupportMaterial.cpp | 4 ++-- src/slic3r/GUI/Field.cpp | 6 ++++-- src/slic3r/GUI/OG_CustomCtrl.cpp | 8 ++++---- src/slic3r/GUI/OptionsGroup.cpp | 30 +++++++++++++++++------------- src/slic3r/GUI/Plater.cpp | 6 +++--- src/slic3r/GUI/Tab.cpp | 2 +- src/slic3r/Utils/Platform.cpp | 2 +- xs/xsp/Config.xsp | 13 ------------- 10 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 9c9b5d348..80d34e821 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1441,6 +1441,24 @@ private: class ConfigOptionDef { public: + enum class GUIType { + undefined, + // Open enums, integer value could be one of the enumerated values or something else. + i_enum_open, + // Open enums, float value could be one of the enumerated values or something else. + f_enum_open, + // Color picker, string value. + color, + // ??? + select_open, + // Currently unused. + slider, + // Static text + legend, + // Vector value, but edited as a single string. + one_string, + }; + // Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map. t_config_option_key opt_key; // What type? bool, int, string etc. @@ -1524,7 +1542,7 @@ public: // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, // "select_open" - to open a selection dialog (currently only a serial port selection). - std::string gui_type; + GUIType gui_type { GUIType::undefined }; // Usually empty. Otherwise "serialized" or "show_value" // The flags may be combined. // "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3aad02f91..f772f354f 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -66,7 +66,7 @@ void PrintConfigDef::init_common_params() def->label = L("G-code thumbnails"); def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\""); def->mode = comExpert; - def->gui_type = "one_string"; + def->gui_type = ConfigOptionDef::GUIType::one_string; def->set_default_value(new ConfigOptionPoints()); def = this->add("layer_height", coFloat); @@ -116,7 +116,7 @@ void PrintConfigDef::init_common_params() def = this->add("printhost_port", coString); def->label = L("Printer"); def->tooltip = L("Name of the printer"); - def->gui_type = "select_open"; + def->gui_type = ConfigOptionDef::GUIType::select_open; def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); @@ -568,7 +568,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(true)); def = this->add("extruder", coInt); - def->gui_type = "i_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Extruder"); def->category = L("Extruders"); def->tooltip = L("The extruder to use (unless more specific extruder settings are specified). " @@ -606,7 +606,7 @@ void PrintConfigDef::init_fff_params() def = this->add("extruder_colour", coStrings); def->label = L("Extruder Color"); def->tooltip = L("This is only used in the Slic3r interface as a visual help."); - def->gui_type = "color"; + def->gui_type = ConfigOptionDef::GUIType::color; // Empty string means no color assigned yet. def->set_default_value(new ConfigOptionStrings { "" }); @@ -668,7 +668,7 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_colour", coStrings); def->label = L("Color"); def->tooltip = L("This is only used in the Slic3r interface as a visual help."); - def->gui_type = "color"; + def->gui_type = ConfigOptionDef::GUIType::color; def->set_default_value(new ConfigOptionStrings { "#29B2B2" }); def = this->add("filament_notes", coStrings); @@ -812,7 +812,7 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_type", coStrings); def->label = L("Filament type"); def->tooltip = L("The filament material type for use in custom G-codes."); - def->gui_type = "f_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->gui_flags = "show_value"; def->enum_values.push_back("PLA"); def->enum_values.push_back("PET"); @@ -881,7 +881,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(45)); def = this->add("fill_density", coPercent); - def->gui_type = "f_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->gui_flags = "show_value"; def->label = L("Fill density"); def->category = L("Infill"); @@ -1168,7 +1168,7 @@ void PrintConfigDef::init_fff_params() "Set this parameter to zero to disable anchoring perimeters connected to a single infill line."); def->sidetext = L("mm or %"); def->ratio_over = "infill_extrusion_width"; - def->gui_type = "f_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->enum_values.push_back("0"); def->enum_values.push_back("1"); def->enum_values.push_back("2"); @@ -1950,7 +1950,7 @@ void PrintConfigDef::init_fff_params() #if 0 def = this->add("seam_preferred_direction", coFloat); -// def->gui_type = "slider"; +// def->gui_type = ConfigOptionDef::GUIType::slider; def->label = L("Direction"); def->sidetext = L("°"); def->full_label = L("Preferred direction of the seam"); @@ -1960,7 +1960,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(0)); def = this->add("seam_preferred_direction_jitter", coFloat); -// def->gui_type = "slider"; +// def->gui_type = ConfigOptionDef::GUIType::slider; def->label = L("Jitter"); def->sidetext = L("°"); def->full_label = L("Seam preferred direction jitter"); @@ -2234,7 +2234,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); def = this->add("support_material_contact_distance", coFloat); - def->gui_type = "f_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->label = L("Top contact Z distance"); def->category = L("Support material"); def->tooltip = L("The vertical distance between object and support material interface. " @@ -2252,7 +2252,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(0.2)); def = this->add("support_material_bottom_contact_distance", coFloat); - def->gui_type = "f_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->label = L("Bottom contact Z distance"); def->category = L("Support material"); def->tooltip = L("The vertical distance between the object top surface and the support material interface. " @@ -2318,7 +2318,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(1)); auto support_material_interface_layers = def = this->add("support_material_interface_layers", coInt); - def->gui_type = "i_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Top interface layers"); def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material."); @@ -2336,7 +2336,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(3)); def = this->add("support_material_bottom_interface_layers", coInt); - def->gui_type = "i_enum_open"; + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Bottom interface layers"); def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material. " @@ -2873,7 +2873,7 @@ void PrintConfigDef::init_sla_params() def = this->add("material_type", coString); def->label = L("SLA material type"); def->tooltip = L("SLA material type"); - def->gui_type = "f_enum_open"; // TODO: ??? + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; // TODO: ??? def->gui_flags = "show_value"; def->enum_values.push_back("Tough"); def->enum_values.push_back("Flexible"); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 8dc939aed..9caac5769 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -2760,7 +2760,7 @@ std::pair -1) { + if (num_interface_layers_top > 0) { // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; coordf_t top_inteface_z = std::numeric_limits::max(); @@ -2781,7 +2781,7 @@ std::pair top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); } } - if (num_interface_layers_bottom > -1) { + if (num_interface_layers_bottom > 0) { // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; coordf_t bottom_interface_z = - std::numeric_limits::max(); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 02ef87775..5af658a21 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -902,7 +902,7 @@ void Choice::BUILD() { if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit); choice_ctrl* temp; - if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0) { + if (m_opt.gui_type != undefined && m_opt.gui_type != ConfigOptionDef::GUIType::select_open) { m_is_editable = true; temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); } @@ -1237,7 +1237,7 @@ boost::any& Choice::get_value() else if (m_opt_id == "brim_type") m_value = static_cast(ret_enum); } - else if (m_opt.gui_type == "f_enum_open") { + else if (m_opt.gui_type == ConfigOptionDef::GUIType::f_enum_open || m_opt.gui_type == ConfigOptionDef::GUIType::i_enum_open) { const int ret_enum = field->GetSelection(); if (ret_enum < 0 || m_opt.enum_values.empty() || m_opt.type == coStrings || (ret_str != m_opt.enum_values[ret_enum] && ret_str != _(m_opt.enum_labels[ret_enum]))) @@ -1245,6 +1245,8 @@ boost::any& Choice::get_value() get_value_by_opt_type(ret_str); else if (m_opt.type == coFloatOrPercent) m_value = m_opt.enum_values[ret_enum]; + else if (m_opt.type == coInt) + m_value = atoi(m_opt.enum_values[ret_enum].c_str()); else m_value = atof(m_opt.enum_values[ret_enum].c_str()); } diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 6433bf2d1..aba3404e7 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -91,7 +91,7 @@ void OG_CustomCtrl::init_ctrl_lines() height = m_bmp_blinking_sz.GetHeight() + m_v_gap; ctrl_lines.emplace_back(CtrlLine(height, this, line, true)); } - else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == "legend") ) + else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == ConfigOptionDef::GUIType::legend) ) { wxSize label_sz = GetTextExtent(line.label); height = label_sz.y * (label_sz.GetWidth() > int(opt_group->label_width * m_em_unit) ? 2 : 1) + m_v_gap; @@ -186,11 +186,11 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) #endif //__WXMSW__ h_pos += label_w + 1 + m_h_gap; } - h_pos += (opt.opt.gui_type == "legend" ? 1 : 3) * blinking_button_width; + h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width; if (field == field_in) break; - if (opt.opt.gui_type == "legend") + if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) h_pos += 2 * blinking_button_width; h_pos += field->getWindow()->GetSize().x; @@ -580,7 +580,7 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) wxBitmap bmp = create_scaled_bitmap(bmp_name, ctrl, wxOSX ? 10 : 12); wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp).GetHeight()) / 2); - if (og_line.get_options().front().opt.gui_type != "legend") + if (og_line.get_options().front().opt.gui_type != ConfigOptionDef::GUIType::legend) dc.DrawBitmap(bmp, 0, y_draw); return get_bitmap_size(bmp).GetWidth() + ctrl->m_h_gap; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index b87e2047d..c81f4c644 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -25,23 +25,27 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id) { const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt) { // Check the gui_type field first, fall through // is the normal type. - if (opt.gui_type == "select") { - } else if (opt.gui_type == "select_open") { + switch (opt.gui_type) { + case ConfigOptionDef::GUIType::select_open: m_fields.emplace(id, Choice::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "color") { + break; + case ConfigOptionDef::GUIType::color: m_fields.emplace(id, ColourPicker::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "f_enum_open" || - opt.gui_type == "i_enum_open" || - opt.gui_type == "i_enum_closed") { + break; + case ConfigOptionDef::GUIType::f_enum_open: + case ConfigOptionDef::GUIType::i_enum_open: m_fields.emplace(id, Choice::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "slider") { + break; + case ConfigOptionDef::GUIType::slider: m_fields.emplace(id, SliderCtrl::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "i_spin") { // Spinctrl - } else if (opt.gui_type == "legend") { // StaticText + break; + case ConfigOptionDef::GUIType::legend: // StaticText m_fields.emplace(id, StaticText::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "one_string") { + break; + case ConfigOptionDef::GUIType::one_string: m_fields.emplace(id, TextCtrl::Create(this->ctrl_parent(), opt, id)); - } else { + break; + default: switch (opt.type) { case coFloatOrPercent: case coFloat: @@ -122,7 +126,7 @@ bool OptionsGroup::is_legend_line() { if (m_lines.size() == 1) { const std::vector