From 2b9741da684329f34141babfc50a808622be6bcb Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 23 Mar 2022 13:10:20 +0100 Subject: [PATCH] Refactoring - running the support placement in gui worker debug UI debug subdivide --- src/libslic3r/CMakeLists.txt | 4 + src/libslic3r/FDMSupportSpots.cpp | 297 ++++++++++++++++ src/libslic3r/FDMSupportSpots.hpp | 73 ++++ src/libslic3r/Subdivide.cpp | 218 ++++++++++++ src/libslic3r/Subdivide.hpp | 12 + src/slic3r/CMakeLists.txt | 3 +- src/slic3r/GUI/Gizmos/Experiment.cpp | 353 ------------------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 305 ++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 9 +- src/slic3r/GUI/Jobs/FDMSupportSpotsJob.cpp | 102 ++++++ src/slic3r/GUI/Jobs/FDMSupportSpotsJob.hpp | 41 +++ 11 files changed, 950 insertions(+), 467 deletions(-) create mode 100644 src/libslic3r/FDMSupportSpots.cpp create mode 100644 src/libslic3r/FDMSupportSpots.hpp create mode 100644 src/libslic3r/Subdivide.cpp create mode 100644 src/libslic3r/Subdivide.hpp delete mode 100644 src/slic3r/GUI/Gizmos/Experiment.cpp create mode 100644 src/slic3r/GUI/Jobs/FDMSupportSpotsJob.cpp create mode 100644 src/slic3r/GUI/Jobs/FDMSupportSpotsJob.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 5e8d681f1a..168617c01c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -50,6 +50,8 @@ set(SLIC3R_SOURCES ExtrusionEntityCollection.hpp ExtrusionSimulator.cpp ExtrusionSimulator.hpp + FDMSupportSpots.cpp + FDMSupportSpots.hpp FileParserError.hpp Fill/Fill.cpp Fill/Fill.hpp @@ -225,6 +227,8 @@ set(SLIC3R_SOURCES SlicesToTriangleMesh.cpp SlicingAdaptive.cpp SlicingAdaptive.hpp + Subdivide.cpp + Subdivide.hpp SupportMaterial.cpp SupportMaterial.hpp Surface.cpp diff --git a/src/libslic3r/FDMSupportSpots.cpp b/src/libslic3r/FDMSupportSpots.cpp new file mode 100644 index 0000000000..cfffb1b6bb --- /dev/null +++ b/src/libslic3r/FDMSupportSpots.cpp @@ -0,0 +1,297 @@ +#include "FDMSupportSpots.hpp" + +namespace Slic3r { + +inline Vec3f value_to_rgbf(float minimum, float maximum, float value) { + float ratio = 2.0f * (value - minimum) / (maximum - minimum); + float b = std::max(0.0f, (1.0f - ratio)); + float r = std::max(0.0f, (ratio - 1.0f)); + float g = 1.0f - b - r; + return Vec3f { r, g, b }; +} + +inline float face_area(const stl_vertex vertex[3]) { + return (vertex[1] - vertex[0]).cross(vertex[2] - vertex[1]).norm() / 2; +} + +inline float its_face_area(const indexed_triangle_set &its, const stl_triangle_vertex_indices face) { + const stl_vertex vertices[3] { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; + return face_area(vertices); +} +inline float its_face_area(const indexed_triangle_set &its, const int face_idx) { + return its_face_area(its, its.indices[face_idx]); +} + +FDMSupportSpots::FDMSupportSpots(FDMSupportSpotsConfig config, indexed_triangle_set mesh, const Transform3d &transform) : + m_config(config) { + using namespace FDMSupportSpotsImpl; + + // Transform and subdivide first + its_transform(mesh, transform); +// m_mesh = its_subdivide(mesh, config.max_side_length); + m_mesh = mesh; + + // Prepare data structures + + auto neighbours = its_face_neighbors_par(mesh); + + for (size_t face_index = 0; face_index < mesh.indices.size(); ++face_index) { + Vec3f normal = its_face_normal(mesh, face_index); + //extract vertices + std::vector vertices { mesh.vertices[mesh.indices[face_index][0]], + mesh.vertices[mesh.indices[face_index][1]], mesh.vertices[mesh.indices[face_index][2]] }; + + //sort vertices by z + std::sort(vertices.begin(), vertices.end(), [](const Vec3f &a, const Vec3f &b) { + return a.z() < b.z(); + }); + + Vec3f lowest_edge_a = (vertices[1] - vertices[0]).normalized(); + Vec3f lowest_edge_b = (vertices[2] - vertices[0]).normalized(); + Vec3f down = -Vec3f::UnitZ(); + + Triangle t { }; + t.indices = mesh.indices[face_index]; + t.normal = normal; + t.downward_dot_value = normal.dot(down); + t.index = face_index; + t.neighbours = neighbours[face_index]; + t.lowest_z_coord = vertices[0].z(); + t.edge_dot_value = std::max(lowest_edge_a.dot(down), lowest_edge_b.dot(down)); + t.area = its_face_area(mesh, face_index); + + this->m_triangles.push_back(t); + this->m_triangle_indexes_by_z.push_back(face_index); + } + + assert(this->m_triangle_indexes_by_z.size() == this->m_triangles.size()); + assert(mesh.indices.size() == this->m_triangles.size()); + + std::sort(this->m_triangle_indexes_by_z.begin(), this->m_triangle_indexes_by_z.end(), + [&](const size_t &left, const size_t &right) { + if (this->m_triangles[left].lowest_z_coord != this->m_triangles[right].lowest_z_coord) { + return this->m_triangles[left].lowest_z_coord < this->m_triangles[right].lowest_z_coord; + } else { + return this->m_triangles[left].edge_dot_value > this->m_triangles[right].edge_dot_value; + } + }); + + for (size_t order_index = 0; order_index < this->m_triangle_indexes_by_z.size(); ++order_index) { + this->m_triangles[this->m_triangle_indexes_by_z[order_index]].order_by_z = order_index; + } + + for (Triangle &triangle : this->m_triangles) { + std::sort(begin(triangle.neighbours), end(triangle.neighbours), [&](const size_t left, const size_t right) { + if (left < 0 || right < 0) { + return true; + } + return this->m_triangles[left].order_by_z < this->m_triangles[right].order_by_z; + }); + } +} + +float FDMSupportSpots::triangle_vertices_shortest_distance(const indexed_triangle_set &its, const size_t &face_a, + const size_t &face_b) const { + float distance = std::numeric_limits::max(); + for (const auto &vertex_a_index : its.indices[face_a]) { + for (const auto &vertex_b_index : its.indices[face_b]) { + distance = std::min(distance, (its.vertices[vertex_a_index] - its.vertices[vertex_b_index]).norm()); + } + } + return distance; +} + +void FDMSupportSpots::find_support_areas() { + using namespace FDMSupportSpotsImpl; + size_t next_group_id = 1; + for (const size_t ¤t_index : this->m_triangle_indexes_by_z) { + Triangle ¤t = this->m_triangles[current_index]; + + size_t group_id = 0; + float neighbourhood_unsupported_area = 0; + bool visited_neighbour = false; + + std::queue neighbours { }; + std::set explored { }; + for (const auto &direct_neighbour_index : current.neighbours) { + if (direct_neighbour_index < 0 || !this->m_triangles[direct_neighbour_index].visited) { + continue; + } + neighbours.push(direct_neighbour_index); + const Triangle &direct_neighbour = this->m_triangles[direct_neighbour_index]; + if (neighbourhood_unsupported_area <= direct_neighbour.unsupported_weight) { + neighbourhood_unsupported_area = direct_neighbour.unsupported_weight; + group_id = direct_neighbour.group_id; + } + visited_neighbour = true; + } + + while (!neighbours.empty() && !visited_neighbour) { + int neighbour_index = neighbours.front(); + neighbours.pop(); + explored.insert(neighbour_index); + + const Triangle &neighbour = this->m_triangles[neighbour_index]; + if (explored.find(neighbour_index) != explored.end() + || triangle_vertices_shortest_distance(this->m_mesh, current.index, neighbour_index) + > this->m_config.islands_tolerance_distance) { + // not visited, already explored, or too far + continue; + } + + if (neighbour.visited) { + visited_neighbour = true; + break; + } + for (const auto &neighbour_index : neighbour.neighbours) { + if (neighbour_index < 0) { + continue; + } + neighbours.push(neighbour_index); + } + + } + + current.visited = true; + current.unsupported_weight = + current.downward_dot_value >= this->m_config.limit_angle_cos ? + current.area + neighbourhood_unsupported_area : 0; + current.group_id = group_id; + + if (current.downward_dot_value > 0 + && (current.unsupported_weight > this->m_config.patch_spacing || !visited_neighbour)) { + group_id = next_group_id; + next_group_id++; + + std::queue supporters { }; + current.visited = false; + supporters.push(int(current_index)); + float supported_size = 0; + while (supported_size < this->m_config.patch_size && !supporters.empty()) { + int s = supporters.front(); + supporters.pop(); + Triangle &supporter = this->m_triangles[s]; + if (supporter.downward_dot_value <= 0.1) { + continue; + } + + if (supporter.visited) { + supported_size += supporter.supports ? supporter.area : 0; + } else { + supporter.supports = true; + supporter.unsupported_weight = 0; + supported_size += supporter.area; + supporter.visited = true; + supporter.group_id = group_id; + for (const auto &n : supporter.neighbours) { + if (n < 0) + continue; + supporters.push(n); + } + } + } + } + + } +} + +#ifdef DEBUG_FILES +void FDMSupportSpots::debug_export() const { + using namespace FDMSupportSpotsImpl; + Slic3r::CNumericLocalesSetter locales_setter; + { + std::string file_name = debug_out_path("groups.obj"); + FILE *fp = boost::nowide::fopen(file_name.c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << file_name << " for writing"; + return; + } + + for (size_t i = 0; i < this->m_triangles.size(); ++i) { + Vec3f color = value_to_rgbf(0.0f, 19.0f, float(this->m_triangles[i].group_id % 20)); + for (size_t index = 0; index < 3; ++index) { + fprintf(fp, "v %f %f %f %f %f %f\n", this->m_mesh.vertices[this->m_triangles[i].indices[index]](0), + this->m_mesh.vertices[this->m_triangles[i].indices[index]](1), + this->m_mesh.vertices[this->m_triangles[i].indices[index]](2), color(0), + color(1), color(2)); + } + fprintf(fp, "f %zu %zu %zu\n", i * 3 + 1, i * 3 + 2, i * 3 + 3); + } + fclose(fp); + } + { + std::string file_name = debug_out_path("sorted.obj"); + FILE *fp = boost::nowide::fopen(file_name.c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << file_name << " for writing"; + return; + } + + for (size_t i = 0; i < this->m_triangle_indexes_by_z.size(); ++i) { + const Triangle &triangle = this->m_triangles[this->m_triangle_indexes_by_z[i]]; + Vec3f color = Vec3f { float(i) / float(this->m_triangle_indexes_by_z.size()), float(i) + / float(this->m_triangle_indexes_by_z.size()), 0.5 }; + for (size_t index = 0; index < 3; ++index) { + fprintf(fp, "v %f %f %f %f %f %f\n", this->m_mesh.vertices[triangle.indices[index]](0), + this->m_mesh.vertices[triangle.indices[index]](1), + this->m_mesh.vertices[triangle.indices[index]](2), color(0), + color(1), color(2)); + } + fprintf(fp, "f %zu %zu %zu\n", i * 3 + 1, i * 3 + 2, i * 3 + 3); + } + fclose(fp); + } + + { + std::string file_name = debug_out_path("weight.obj"); + FILE *fp = boost::nowide::fopen(file_name.c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << file_name << " for writing"; + return; + } + + for (size_t i = 0; i < this->m_triangle_indexes_by_z.size(); ++i) { + const Triangle &triangle = this->m_triangles[this->m_triangle_indexes_by_z[i]]; + Vec3f color = value_to_rgbf(0, 10, triangle.area); + for (size_t index = 0; index < 3; ++index) { + fprintf(fp, "v %f %f %f %f %f %f\n", this->m_mesh.vertices[triangle.indices[index]](0), + this->m_mesh.vertices[triangle.indices[index]](1), + this->m_mesh.vertices[triangle.indices[index]](2), color(0), + color(1), color(2)); + } + fprintf(fp, "f %zu %zu %zu\n", i * 3 + 1, i * 3 + 2, i * 3 + 3); + } + fclose(fp); + } + + { + std::string file_name = debug_out_path("dot_value.obj"); + FILE *fp = boost::nowide::fopen(file_name.c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << file_name << " for writing"; + return; + } + + for (size_t i = 0; i < this->m_triangle_indexes_by_z.size(); ++i) { + const Triangle &triangle = this->m_triangles[this->m_triangle_indexes_by_z[i]]; + Vec3f color = value_to_rgbf(-1, 1, triangle.downward_dot_value); + for (size_t index = 0; index < 3; ++index) { + fprintf(fp, "v %f %f %f %f %f %f\n", this->m_mesh.vertices[triangle.indices[index]](0), + this->m_mesh.vertices[triangle.indices[index]](1), + this->m_mesh.vertices[triangle.indices[index]](2), color(0), + color(1), color(2)); + } + fprintf(fp, "f %zu %zu %zu\n", i * 3 + 1, i * 3 + 2, i * 3 + 3); + } + fclose(fp); + } +} +#endif + +} + + diff --git a/src/libslic3r/FDMSupportSpots.hpp b/src/libslic3r/FDMSupportSpots.hpp new file mode 100644 index 0000000000..b7d90dfebe --- /dev/null +++ b/src/libslic3r/FDMSupportSpots.hpp @@ -0,0 +1,73 @@ +#ifndef SRC_LIBSLIC3R_FDMSUPPORTSPOTS_HPP_ +#define SRC_LIBSLIC3R_FDMSUPPORTSPOTS_HPP_ + +#include "libslic3r/Model.hpp" + +#include +#include + +#include +#include + +#define DEBUG_FILES + +#ifdef DEBUG_FILES +#include +#include "libslic3r/Utils.hpp" +#endif + +namespace Slic3r { + +namespace FDMSupportSpotsImpl { +struct Triangle { + stl_triangle_vertex_indices indices; + Vec3f normal; + float downward_dot_value; + size_t index; + Vec3i neighbours; + float lowest_z_coord; + // higher value of dot product of the downward direction and the two bottom edges + float edge_dot_value; + float area; + + size_t order_by_z; + + //members updated during algorithm + float unsupported_weight { 0.0 }; + bool supports = false; + bool visited = false; + size_t group_id = 0; +}; +} + +struct FDMSupportSpotsConfig { + float limit_angle_cos { 35.0f * PI / 180.0f }; + float patch_size { 6.0f }; + float patch_spacing { 6.0f }; + float islands_tolerance_distance { 1.0f }; + float max_side_length { 1.0f }; +}; + +struct FDMSupportSpots { + FDMSupportSpotsConfig m_config; + indexed_triangle_set m_mesh; + std::vector m_triangles; + std::vector m_triangle_indexes_by_z; + + explicit FDMSupportSpots(FDMSupportSpotsConfig config, indexed_triangle_set mesh, const Transform3d& transform); + + float triangle_vertices_shortest_distance(const indexed_triangle_set &its, const size_t &face_a, + const size_t &face_b) const; + + void find_support_areas(); + +#ifdef DEBUG_FILES + void debug_export() const; +#endif + +} +; + +} + +#endif /* SRC_LIBSLIC3R_FDMSUPPORTSPOTS_HPP_ */ diff --git a/src/libslic3r/Subdivide.cpp b/src/libslic3r/Subdivide.cpp new file mode 100644 index 0000000000..31988a851e --- /dev/null +++ b/src/libslic3r/Subdivide.cpp @@ -0,0 +1,218 @@ +#include "Subdivide.hpp" +#include "Point.hpp" + +namespace Slic3r{ + +indexed_triangle_set its_subdivide( + const indexed_triangle_set &its, float max_length) +{ + // same order as key order in Edge Divides + struct VerticesSequence + { + size_t start_index; + bool positive_order; + VerticesSequence(size_t start_index, bool positive_order = true) + : start_index(start_index), positive_order(positive_order){} + }; + // vertex index small, big vertex index from key.first to key.second + using EdgeDivides = std::map, VerticesSequence>; + struct Edges + { + Vec3f data[3]; + Vec3f lengths; + Edges(const Vec3crd &indices, const std::vector &vertices) + : lengths(-1.f,-1.f,-1.f) + { + const Vec3f &v0 = vertices[indices[0]]; + const Vec3f &v1 = vertices[indices[1]]; + const Vec3f &v2 = vertices[indices[2]]; + data[0] = v0 - v1; + data[1] = v1 - v2; + data[2] = v2 - v0; + } + float abs_sum(const Vec3f &v) + { + return abs(v[0]) + abs(v[1]) + abs(v[2]); + } + bool is_dividable(const float& max_length) { + Vec3f sum(abs_sum(data[0]), abs_sum(data[1]), abs_sum(data[2])); + Vec3i biggest_index = (sum[0] > sum[1]) ? + ((sum[0] > sum[2]) ? + ((sum[2] > sum[1]) ? + Vec3i(0, 2, 1) : + Vec3i(0, 1, 2)) : + Vec3i(2, 0, 1)) : + ((sum[1] > sum[2]) ? + ((sum[2] > sum[0]) ? + Vec3i(1, 2, 0) : + Vec3i(1, 0, 2)) : + Vec3i(2, 1, 0)); + for (int i = 0; i < 3; i++) { + int index = biggest_index[i]; + if (sum[index] <= max_length) return false; + lengths[index] = data[index].norm(); + if (lengths[index] <= max_length) continue; + + // calculate rest of lengths + for (int j = i + 1; j < 3; j++) { + index = biggest_index[j]; + lengths[index] = data[index].norm(); + } + return true; + } + return false; + } + }; + struct TriangleLengths + { + Vec3crd indices; + Vec3f l; // lengths + TriangleLengths(const Vec3crd &indices, const Vec3f &lengths) + : indices(indices), l(lengths) + {} + + int get_divide_index(float max_length) { + if (l[0] > l[1] && l[0] > l[2]) { + if (l[0] > max_length) return 0; + } else if (l[1] > l[2]) { + if (l[1] > max_length) return 1; + } else { + if (l[2] > max_length) return 2; + } + return -1; + } + + // divide triangle add new vertex to vertices + std::pair divide( + int divide_index, float max_length, + std::vector &vertices, + EdgeDivides &edge_divides) + { + // index to lengths and indices + size_t i0 = divide_index; + size_t i1 = (divide_index + 1) % 3; + size_t vi0 = indices[i0]; + size_t vi1 = indices[i1]; + std::pair key(vi0, vi1); + bool key_swap = false; + if (key.first > key.second) { + std::swap(key.first, key.second); + key_swap = true; + } + + float length = l[divide_index]; + size_t count_edge_vertices = static_cast(floor(length / max_length)); + float count_edge_segments = static_cast(count_edge_vertices + 1); + + auto it = edge_divides.find(key); + if (it == edge_divides.end()) { + // Create new vertices + VerticesSequence new_vs(vertices.size()); + Vec3f vf = vertices[key.first]; // copy + const Vec3f &vs = vertices[key.second]; + Vec3f dir = vs - vf; + for (size_t i = 1; i <= count_edge_vertices; ++i) { + float ratio = i / count_edge_segments; + vertices.push_back(vf + dir * ratio); + } + bool success; + std::tie(it,success) = edge_divides.insert({key, new_vs}); + assert(success); + } + const VerticesSequence &vs = it->second; + + int index_offset = count_edge_vertices/2; + size_t i2 = (divide_index + 2) % 3; + if (count_edge_vertices % 2 == 0 && key_swap == (l[i1] < l[i2])) { + --index_offset; + } + int sign = (vs.positive_order) ? 1 : -1; + size_t new_index = vs.start_index + sign*index_offset; + + size_t vi2 = indices[i2]; + const Vec3f &v2 = vertices[vi2]; + Vec3f new_edge = v2 - vertices[new_index]; + float new_len = new_edge.norm(); + + float ratio = (1 + index_offset) / count_edge_segments; + float len1 = l[i0] * ratio; + float len2 = l[i0] - len1; + if (key_swap) std::swap(len1, len2); + + Vec3crd indices1(vi0, new_index, vi2); + Vec3f lengths1(len1, new_len, l[i2]); + + Vec3crd indices2(new_index, vi1, vi2); + Vec3f lengths2(len2, l[i1], new_len); + + // append key for divided edge when neccesary + if (index_offset > 0) { + std::pair new_key(key.first, new_index); + bool new_key_swap = false; + if (new_key.first > new_key.second) { + std::swap(new_key.first, new_key.second); + new_key_swap = true; + } + if (edge_divides.find(new_key) == edge_divides.end()) { + // insert new + edge_divides.insert({new_key, (new_key_swap) ? + VerticesSequence(new_index - sign, !vs.positive_order) + : vs}); + } + } + + if (index_offset < int(count_edge_vertices)-1) { + std::pair new_key(new_index, key.second); + bool new_key_swap = false; + if (new_key.first > new_key.second) { + std::swap(new_key.first, new_key.second); + new_key_swap = true; + } + // bad order + if (edge_divides.find(new_key) == edge_divides.end()) { + edge_divides.insert({new_key, (new_key_swap) ? + VerticesSequence(vs.start_index + sign*(count_edge_vertices-1), !vs.positive_order) + : VerticesSequence(new_index + sign, vs.positive_order)}); + } + } + + return {TriangleLengths(indices1, lengths1), + TriangleLengths(indices2, lengths2)}; + } + }; + indexed_triangle_set result; + result.indices.reserve(its.indices.size()); + const std::vector &vertices = its.vertices; + result.vertices = vertices; // copy + std::queue tls; + + EdgeDivides edge_divides; + for (const Vec3crd &indices : its.indices) { + Edges edges(indices, vertices); + // speed up only sum not sqrt is apply + if (!edges.is_dividable(max_length)) { + // small triangle + result.indices.push_back(indices); + continue; + } + TriangleLengths tl(indices, edges.lengths); + do { + int divide_index = tl.get_divide_index(max_length); + if (divide_index < 0) { + // no dividing + result.indices.push_back(tl.indices); + if (tls.empty()) break; + tl = tls.front(); // copy + tls.pop(); + } else { + auto [tl1, tl2] = tl.divide(divide_index, max_length, + result.vertices, edge_divides); + tl = tl1; + tls.push(tl2); + } + } while (true); + } + return result; +} + +} diff --git a/src/libslic3r/Subdivide.hpp b/src/libslic3r/Subdivide.hpp new file mode 100644 index 0000000000..f97e4b3c5c --- /dev/null +++ b/src/libslic3r/Subdivide.hpp @@ -0,0 +1,12 @@ +#ifndef libslic3r_Subdivide_hpp_ +#define libslic3r_Subdivide_hpp_ + +#include "TriangleMesh.hpp" + +namespace Slic3r { + +indexed_triangle_set its_subdivide(const indexed_triangle_set &its, float max_length); + +} + +#endif //libslic3r_Subdivide_hpp_ diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index cad74f9e15..6e1ce74c7b 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -47,7 +47,6 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoSlaSupports.hpp GUI/Gizmos/GLGizmoFdmSupports.cpp GUI/Gizmos/GLGizmoFdmSupports.hpp - GUI/Gizmos/Experiment.cpp GUI/Gizmos/GLGizmoFlatten.cpp GUI/Gizmos/GLGizmoFlatten.hpp GUI/Gizmos/GLGizmoCut.cpp @@ -179,6 +178,8 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/ArrangeJob.cpp GUI/Jobs/RotoptimizeJob.hpp GUI/Jobs/RotoptimizeJob.cpp + GUI/Jobs/FDMSupportSpotsJob.hpp + GUI/Jobs/FDMSupportSpotsJob.cpp GUI/Jobs/FillBedJob.hpp GUI/Jobs/FillBedJob.cpp GUI/Jobs/SLAImportJob.hpp diff --git a/src/slic3r/GUI/Gizmos/Experiment.cpp b/src/slic3r/GUI/Gizmos/Experiment.cpp deleted file mode 100644 index 1c89cb299d..0000000000 --- a/src/slic3r/GUI/Gizmos/Experiment.cpp +++ /dev/null @@ -1,353 +0,0 @@ -#include "libslic3r/Model.hpp" - -#include -#include - -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" -#include "libslic3r/TriangleSelector.hpp" - -#include -#include - -#define DEBUG_FILES - -#ifdef DEBUG_FILES -#include -#include "libslic3r/Utils.hpp" -#endif - -namespace Slic3r::GUI { - -//TODO close in seprate namespace - -struct Triangle { - stl_triangle_vertex_indices indices; - Vec3f normal; - float downward_dot_value; - size_t index; - Vec3i neighbours; - float lowest_z_coord; - // higher value of dot product of the downward direction and the two bottom edges - float edge_dot_value; - float area; - - size_t order_by_z; - - //members updated during algorithm - float unsupported_weight { 0.0 }; - bool supports = false; - bool visited = false; - size_t group_id = 0; -}; - -inline Vec3f value_to_rgbf(float minimum, float maximum, float value) { - float ratio = 2.0f * (value - minimum) / (maximum - minimum); - float b = std::max(0.0f, (1.0f - ratio)); - float r = std::max(0.0f, (ratio - 1.0f)); - float g = 1.0f - b - r; - return Vec3f { r, g, b }; -} - -inline float face_area(const stl_vertex vertex[3]) { - return (vertex[1] - vertex[0]).cross(vertex[2] - vertex[1]).norm() / 2; -} - -inline float its_face_area(const indexed_triangle_set &its, const stl_triangle_vertex_indices face) { - const stl_vertex vertices[3] { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; - return face_area(vertices); -} -inline float its_face_area(const indexed_triangle_set &its, const int face_idx) { - return its_face_area(its, its.indices[face_idx]); -} - -struct SupportPlacerMesh { - const float unsupported_area_limit; - const float patch_size; - const float limit_angle_cos; - const float neighbour_max_distance = 2.0; - - indexed_triangle_set mesh; - std::vector triangles; - std::vector triangle_indexes_by_z; - - explicit SupportPlacerMesh(indexed_triangle_set &&t_mesh, float dot_limit, float patch_size, - float unsupported_area_limit) : - mesh(t_mesh), limit_angle_cos(dot_limit), patch_size(patch_size), unsupported_area_limit( - unsupported_area_limit) { - auto neighbours = its_face_neighbors_par(mesh); - - for (size_t face_index = 0; face_index < mesh.indices.size(); ++face_index) { - Vec3f normal = its_face_normal(mesh, face_index); - //extract vertices - std::vector vertices { mesh.vertices[mesh.indices[face_index][0]], - mesh.vertices[mesh.indices[face_index][1]], mesh.vertices[mesh.indices[face_index][2]] }; - - //sort vertices by z - std::sort(vertices.begin(), vertices.end(), [](const Vec3f &a, const Vec3f &b) { - return a.z() < b.z(); - }); - - Vec3f lowest_edge_a = (vertices[1] - vertices[0]).normalized(); - Vec3f lowest_edge_b = (vertices[2] - vertices[0]).normalized(); - Vec3f down = -Vec3f::UnitZ(); - - Triangle t { }; - t.indices = mesh.indices[face_index]; - t.normal = normal; - t.downward_dot_value = normal.dot(down); - t.index = face_index; - t.neighbours = neighbours[face_index]; - t.lowest_z_coord = vertices[0].z(); - t.edge_dot_value = std::max(lowest_edge_a.dot(down), lowest_edge_b.dot(down)); - t.area = its_face_area(mesh, face_index); - - triangles.push_back(t); - triangle_indexes_by_z.push_back(face_index); - } - - assert(triangle_indexes_by_z.size() == triangles.size()); - assert(mesh.indices.size() == triangles.size()); - - std::sort(triangle_indexes_by_z.begin(), triangle_indexes_by_z.end(), - [&](const size_t &left, const size_t &right) { - if (triangles[left].lowest_z_coord != triangles[right].lowest_z_coord) { - return triangles[left].lowest_z_coord < triangles[right].lowest_z_coord; - } else { - return triangles[left].edge_dot_value > triangles[right].edge_dot_value; - } - }); - - for (size_t order_index = 0; order_index < triangle_indexes_by_z.size(); ++order_index) { - triangles[triangle_indexes_by_z[order_index]].order_by_z = order_index; - } - - for (Triangle &triangle : triangles) { - std::sort(begin(triangle.neighbours), end(triangle.neighbours), [&](const size_t left, const size_t right) { - if (left < 0 || right < 0) { - return true; - } - return triangles[left].order_by_z < triangles[right].order_by_z; - }); - } - } - - float triangle_vertices_shortest_distance(const indexed_triangle_set &its, const size_t &face_a, - const size_t &face_b) { - float distance = std::numeric_limits::max(); - for (const auto &vertex_a_index : its.indices[face_a]) { - for (const auto &vertex_b_index : its.indices[face_b]) { - distance = std::min(distance, (its.vertices[vertex_a_index] - its.vertices[vertex_b_index]).norm()); - } - } - return distance; - } - - void find_support_areas() { - size_t next_group_id = 1; - for (const size_t ¤t_index : triangle_indexes_by_z) { - Triangle ¤t = triangles[current_index]; - - size_t group_id = 0; - float neighbourhood_unsupported_area = 0; - bool visited_neighbour = false; - - std::queue neighbours { }; - std::set explored { }; - for (const auto &direct_neighbour_index : current.neighbours) { - if (direct_neighbour_index < 0 || !triangles[direct_neighbour_index].visited) { - continue; - } - neighbours.push(direct_neighbour_index); - const Triangle &direct_neighbour = triangles[direct_neighbour_index]; - if (neighbourhood_unsupported_area <= direct_neighbour.unsupported_weight) { - neighbourhood_unsupported_area = direct_neighbour.unsupported_weight; - group_id = direct_neighbour.group_id; - } - visited_neighbour = true; - } - - while (!neighbours.empty() && !visited_neighbour) { - int neighbour_index = neighbours.front(); - neighbours.pop(); - explored.insert(neighbour_index); - - const Triangle &neighbour = triangles[neighbour_index]; - if (explored.find(neighbour_index) != explored.end() - || triangle_vertices_shortest_distance(mesh, current.index, neighbour_index) - > neighbour_max_distance) { - // not visited, already explored, or too far - continue; - } - - if (neighbour.visited) { - visited_neighbour = true; - break; - } - for (const auto &neighbour_index : neighbour.neighbours) { - if (neighbour_index < 0) { - continue; - } - neighbours.push(neighbour_index); - } - - } - - current.visited = true; - current.unsupported_weight = - current.downward_dot_value >= limit_angle_cos ? current.area + neighbourhood_unsupported_area : 0; - current.group_id = group_id; - - if (current.downward_dot_value > 0 - && (current.unsupported_weight > unsupported_area_limit || !visited_neighbour)) { - group_id = next_group_id; - next_group_id++; - - std::queue supporters { }; - current.visited = false; - supporters.push(int(current_index)); - float supported_size = 0; - while (supported_size < patch_size && !supporters.empty()) { - int s = supporters.front(); - supporters.pop(); - Triangle &supporter = triangles[s]; - if (supporter.downward_dot_value <= 0.1) { - continue; - } - - if (supporter.visited) { - supported_size += supporter.supports ? supporter.area : 0; - } else { - supporter.supports = true; - supporter.unsupported_weight = 0; - supported_size += supporter.area; - supporter.visited = true; - supporter.group_id = group_id; - for (const auto &n : supporter.neighbours) { - if (n < 0) - continue; - supporters.push(n); - } - } - } - } - - } - } - -#ifdef DEBUG_FILES - void debug_export() const { - Slic3r::CNumericLocalesSetter locales_setter; - { - std::string file_name = debug_out_path("groups.obj"); - FILE *fp = boost::nowide::fopen(file_name.c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << file_name << " for writing"; - return; - } - - for (size_t i = 0; i < triangles.size(); ++i) { - Vec3f color = value_to_rgbf(0.0f, 19.0f, float(triangles[i].group_id % 20)); - for (size_t index = 0; index < 3; ++index) { - fprintf(fp, "v %f %f %f %f %f %f\n", mesh.vertices[triangles[i].indices[index]](0), - mesh.vertices[triangles[i].indices[index]](1), - mesh.vertices[triangles[i].indices[index]](2), color(0), - color(1), color(2)); - } - fprintf(fp, "f %zu %zu %zu\n", i * 3 + 1, i * 3 + 2, i * 3 + 3); - } - fclose(fp); - } - { - std::string file_name = debug_out_path("sorted.obj"); - FILE *fp = boost::nowide::fopen(file_name.c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << file_name << " for writing"; - return; - } - - for (size_t i = 0; i < triangle_indexes_by_z.size(); ++i) { - const Triangle &triangle = triangles[triangle_indexes_by_z[i]]; - Vec3f color = Vec3f { float(i) / float(triangle_indexes_by_z.size()), float(i) - / float(triangle_indexes_by_z.size()), 0.5 }; - for (size_t index = 0; index < 3; ++index) { - fprintf(fp, "v %f %f %f %f %f %f\n", mesh.vertices[triangle.indices[index]](0), - mesh.vertices[triangle.indices[index]](1), - mesh.vertices[triangle.indices[index]](2), color(0), - color(1), color(2)); - } - fprintf(fp, "f %zu %zu %zu\n", i * 3 + 1, i * 3 + 2, i * 3 + 3); - } - fclose(fp); - } - - { - std::string file_name = debug_out_path("weight.obj"); - FILE *fp = boost::nowide::fopen(file_name.c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << file_name << " for writing"; - return; - } - - for (size_t i = 0; i < triangle_indexes_by_z.size(); ++i) { - const Triangle &triangle = triangles[triangle_indexes_by_z[i]]; - Vec3f color = value_to_rgbf(0, 10, triangle.area); - for (size_t index = 0; index < 3; ++index) { - fprintf(fp, "v %f %f %f %f %f %f\n", mesh.vertices[triangle.indices[index]](0), - mesh.vertices[triangle.indices[index]](1), - mesh.vertices[triangle.indices[index]](2), color(0), - color(1), color(2)); - } - fprintf(fp, "f %zu %zu %zu\n", i * 3 + 1, i * 3 + 2, i * 3 + 3); - } - fclose(fp); - } - - { - std::string file_name = debug_out_path("dot_value.obj"); - FILE *fp = boost::nowide::fopen(file_name.c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << file_name << " for writing"; - return; - } - - for (size_t i = 0; i < triangle_indexes_by_z.size(); ++i) { - const Triangle &triangle = triangles[triangle_indexes_by_z[i]]; - Vec3f color = value_to_rgbf(-1, 1, triangle.downward_dot_value); - for (size_t index = 0; index < 3; ++index) { - fprintf(fp, "v %f %f %f %f %f %f\n", mesh.vertices[triangle.indices[index]](0), - mesh.vertices[triangle.indices[index]](1), - mesh.vertices[triangle.indices[index]](2), color(0), - color(1), color(2)); - } - fprintf(fp, "f %zu %zu %zu\n", i * 3 + 1, i * 3 + 2, i * 3 + 3); - } - fclose(fp); - } - } -#endif - -} -; - -inline void do_experimental_support_placement(indexed_triangle_set mesh, TriangleSelectorGUI *selector, - float dot_limit) { - SupportPlacerMesh support_placer { std::move(mesh), dot_limit, 3, 6 }; - - support_placer.find_support_areas(); - - for (const Triangle &t : support_placer.triangles) { - if (t.supports) { - selector->set_facet(t.index, EnforcerBlockerType::ENFORCER); - selector->request_update_render_data(); - } - } - - support_placer.debug_export(); -} - -} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 9ece56de37..361678c827 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -1,6 +1,7 @@ #include "GLGizmoFdmSupports.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/Subdivide.hpp" //#include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" @@ -10,15 +11,12 @@ #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/UndoRedo.hpp" - +#include "slic3r/GUI/Jobs/FDMSupportSpotsJob.hpp" #include - namespace Slic3r::GUI { - - void GLGizmoFdmSupports::on_shutdown() { m_highlight_by_angle_threshold_deg = 0.f; @@ -26,44 +24,40 @@ void GLGizmoFdmSupports::on_shutdown() m_parent.toggle_model_objects_visibility(true); } - - std::string GLGizmoFdmSupports::on_get_name() const { return _u8L("Paint-on supports"); } - - bool GLGizmoFdmSupports::on_init() { m_shortcut_key = WXK_CONTROL_L; m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size"] = _L("Brush size") + ": "; - m_desc["cursor_type"] = _L("Brush shape") + ": "; - m_desc["enforce_caption"] = _L("Left mouse button") + ": "; - m_desc["enforce"] = _L("Enforce supports"); - m_desc["block_caption"] = _L("Right mouse button") + ": "; - m_desc["block"] = _L("Block supports"); - m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; - m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all selection"); - m_desc["circle"] = _L("Circle"); - m_desc["sphere"] = _L("Sphere"); - m_desc["pointer"] = _L("Triangles"); + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["cursor_size"] = _L("Brush size") + ": "; + m_desc["cursor_type"] = _L("Brush shape") + ": "; + m_desc["enforce_caption"] = _L("Left mouse button") + ": "; + m_desc["enforce"] = _L("Enforce supports"); + m_desc["block_caption"] = _L("Right mouse button") + ": "; + m_desc["block"] = _L("Block supports"); + m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _L("Remove selection"); + m_desc["remove_all"] = _L("Remove all selection"); + m_desc["circle"] = _L("Circle"); + m_desc["sphere"] = _L("Sphere"); + m_desc["pointer"] = _L("Triangles"); m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); - m_desc["enforce_button"] = _L("Enforce"); - m_desc["cancel"] = _L("Cancel"); + m_desc["enforce_button"] = _L("Enforce"); + m_desc["cancel"] = _L("Cancel"); - m_desc["tool_type"] = _L("Tool type") + ": "; - m_desc["tool_brush"] = _L("Brush"); - m_desc["tool_smart_fill"] = _L("Smart fill"); + m_desc["tool_type"] = _L("Tool type") + ": "; + m_desc["tool_brush"] = _L("Brush"); + m_desc["tool_smart_fill"] = _L("Smart fill"); m_desc["smart_fill_angle"] = _L("Smart fill angle"); - m_desc["split_triangles"] = _L("Split triangles"); + m_desc["split_triangles"] = _L("Split triangles"); m_desc["on_overhangs_only"] = _L("On overhangs only"); return true; @@ -71,7 +65,7 @@ bool GLGizmoFdmSupports::on_init() void GLGizmoFdmSupports::render_painter_gizmo() { - const Selection& selection = m_parent.get_selection(); + const Selection &selection = m_parent.get_selection(); glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -84,29 +78,30 @@ void GLGizmoFdmSupports::render_painter_gizmo() glsafe(::glDisable(GL_BLEND)); } - - void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) -{ - if (! m_c->selection_info()->model_object()) + { + if (!m_c->selection_info()->model_object()) return; const float approx_height = m_imgui->scaled(23.f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->begin(get_name(), + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + + m_imgui->scaled(1.f); const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); - const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); + const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), + autoset_slider_label_max_width).x + m_imgui->scaled(1.f); - const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); @@ -115,52 +110,110 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f); const float minimal_slider_width = m_imgui->scaled(4.f); - const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); - const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); + const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + + m_imgui->scaled(2.5f); - const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); - const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + + m_imgui->scaled(2.5f); + const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + + m_imgui->scaled(2.5f); - float caption_max = 0.f; + float caption_max = 0.f; float total_text_max = 0.f; - for (const auto &t : std::array{"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); + for (const auto &t : std::array { "enforce", "block", "remove" }) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); } total_text_max += caption_max + m_imgui->scaled(1.f); - caption_max += m_imgui->scaled(1.f); + caption_max += m_imgui->scaled(1.f); - const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); - const float slider_icon_width = m_imgui->get_slider_icon_size().x; - float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; + const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), + std::max(cursor_slider_left, clipping_slider_left)); + const float slider_icon_width = m_imgui->get_slider_icon_size().x; + float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); window_width = std::max(window_width, split_triangles_checkbox_width); window_width = std::max(window_width, on_overhangs_only_checkbox_width); - window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); + window_width = std::max(window_width, + cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); - auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { + auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption); ImGui::SameLine(caption_max); m_imgui->text(text); }; - for (const auto &t : std::array{"enforce", "block", "remove"}) + for (const auto &t : std::array { "enforce", "block", "remove" }) draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + ImGui::Separator(); + { + if (m_imgui->button("Subdivide")) { + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + for (ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + auto new_its = its_subdivide(mv->mesh().its, 1.0f); + mv->set_mesh(new_its); + mv->set_new_unique_id(); + } + update_model_object(); + m_parent.set_as_dirty(); + } + ImGui::NewLine(); + + std::string format_str = std::string("%.f") + I18N::translate_utf8("°", + "Degree sign to use in the respective slider in FDM supports gizmo," + "placed after the number with no whitespace in between."); + + m_imgui->slider_float("##angle_threshold_deg", &m_smart_support_limit_angle_deg, 0.f, 90.f, format_str.data(), + 1.0f, true); + m_imgui->slider_float("##patch_size", &m_smart_support_patch_size, 0.f, 20.f, format_str.data(), + 1.0f, false); + m_imgui->slider_float("##patch_spacing", &m_smart_support_patch_spacing, 0.f, 20.f, format_str.data(), + 1.0f, false); + m_imgui->slider_float("##island_tolerance", &m_smart_support_islands_tolerance, 0.f, 10.f, format_str.data(), + 1.0f, false); + + ImGui::NewLine(); + ImGui::SameLine(window_width - buttons_width - m_imgui->scaled(0.5f)); + if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { + compute_smart_support_placement(m_smart_support_limit_angle_deg, m_smart_support_patch_size, + m_smart_support_patch_spacing, m_smart_support_islands_tolerance); + } + + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); + ModelObject *mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + m_triangle_selectors[idx]->request_update_render_data(); + } + + update_model_object(); + m_parent.set_as_dirty(); + } + + } + ImGui::Separator(); float position_before_text_y = ImGui::GetCursorPos().y; ImGui::AlignTextToFramePadding(); m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); ImGui::AlignTextToFramePadding(); - float position_after_text_y = ImGui::GetCursorPos().y; + float position_after_text_y = ImGui::GetCursorPos().y; std::string format_str = std::string("%.f") + I18N::translate_utf8("°", - "Degree sign to use in the respective slider in FDM supports gizmo," - "placed after the number with no whitespace in between."); + "Degree sign to use in the respective slider in FDM supports gizmo," + "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); float slider_height = m_imgui->get_slider_float_height(); @@ -169,11 +222,15 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SetCursorPosY(slider_start_position_y); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " - "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]); - if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) { + wxString tooltip = + format_wxstr( + _L( + "Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " + "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]); + if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, + format_str.data(), 1.0f, true, tooltip)) { m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); - if (! m_parent.is_using_slope()) { + if (!m_parent.is_using_slope()) { m_parent.use_slope(true); m_parent.set_as_dirty(); } @@ -186,7 +243,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); ImGui::NewLine(); - ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); + ImGui::SameLine(window_width - 2.f * buttons_width - m_imgui->scaled(0.5f)); if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); m_highlight_by_angle_threshold_deg = 0.f; @@ -199,13 +256,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } m_imgui->disabled_end(); - ImGui::Separator(); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["tool_type"]); - float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f; + float tool_type_offset = tool_type_radio_left + + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + + m_imgui->scaled(0.5f)) / 2.f; ImGui::SameLine(tool_type_offset); ImGui::PushItemWidth(tool_type_radio_brush); if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) @@ -220,11 +278,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_tool_type = ToolType::SMART_FILL; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), + max_tooltip_width); m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); if (ImGui::IsItemHovered()) - m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); + m_imgui->tooltip( + format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), + max_tooltip_width); ImGui::Separator(); @@ -232,7 +293,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_imgui->text(m_desc.at("cursor_type")); ImGui::NewLine(); - float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; + float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle + - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; ImGui::SameLine(cursor_type_offset); ImGui::PushItemWidth(cursor_type_radio_sphere); if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) @@ -259,18 +321,22 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (ImGui::IsItemHovered()) m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); - m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); + m_imgui->disabled_begin( + m_cursor_type != TriangleSelector::CursorType::SPHERE + && m_cursor_type != TriangleSelector::CursorType::CIRCLE); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); + m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, + _L("Alt + Mouse wheel")); m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width); + m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), + max_tooltip_width); m_imgui->disabled_end(); } else { @@ -280,7 +346,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) + if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, + format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); @@ -294,9 +361,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } else { if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); + wxGetApp().CallAfter([this]() { + m_c->object_clipper()->set_position(-1., false); + }); } } @@ -309,8 +376,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); - ModelObject *mo = m_c->selection_info()->model_object(); - int idx = -1; + ModelObject *mo = m_c->selection_info()->model_object(); + int idx = -1; for (ModelVolume *mv : mo->volumes) if (mv->is_model_part()) { ++idx; @@ -325,93 +392,111 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_imgui->end(); } - - void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) -{ - float threshold = (float(M_PI)/180.f)*threshold_deg; - const Selection& selection = m_parent.get_selection(); - const ModelObject* mo = m_c->selection_info()->model_object(); - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + { + float threshold = (float(M_PI) / 180.f) * threshold_deg; + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) continue; ++mesh_id; const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); - Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); - Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); + Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); + Vec3f limit = + (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); float dot_limit = limit.dot(down); - - indexed_triangle_set mesh_triangles = mv->mesh().its; - its_transform(mesh_triangles, mi->get_matrix(true)); - do_experimental_support_placement(std::move(mesh_triangles), m_triangle_selectors[mesh_id].get(), dot_limit); - - if (false) { // Now calculate dot product of vert_direction and facets' normals. int idx = 0; const indexed_triangle_set &its = mv->mesh().its; for (const stl_triangle_vertex_indices &face : its.indices) { if (its_face_normal(its, face).dot(down) > dot_limit) { - m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); + m_triangle_selectors[mesh_id]->set_facet(idx, + block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); m_triangle_selectors.back()->request_update_render_data(); } - ++ idx; - } + ++idx; } } Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle") - : _L("Add supports by angle")); + : + _L("Add supports by angle")); update_model_object(); m_parent.set_as_dirty(); } +void GLGizmoFdmSupports::compute_smart_support_placement(float limit_angle_deg, float patch_size, float patch_spacing, float islands_tolerance) { + float threshold = (float(M_PI) / 180.f) * limit_angle_deg; + FDMSupportSpotsConfig support_spots_config { + threshold, patch_size, patch_spacing, islands_tolerance + }; + + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + + std::unordered_map data { }; + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) + continue; + Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); + SupportedMeshData smd { mv->mesh().its, trafo_matrix }; + data.emplace(mv->id().id, smd); + } + + Plater *plater = wxGetApp().plater(); + std::unique_ptr job = std::make_unique(plater, support_spots_config, + mo->id(), mi->id(), data); + + queue_job(plater->get_ui_job_worker(), std::move(job)); + +} void GLGizmoFdmSupports::update_model_object() const { bool updated = false; - ModelObject* mo = m_c->selection_info()->model_object(); + ModelObject *mo = m_c->selection_info()->model_object(); int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) + for (ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) continue; ++idx; updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); } if (updated) { - const ModelObjectPtrs& mos = wxGetApp().model().objects; + const ModelObjectPtrs &mos = wxGetApp().model().objects; wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } } - - void GLGizmoFdmSupports::update_from_model_object() { wxBusyCursor wait; - const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelObject *mo = m_c->selection_info()->model_object(); m_triangle_selectors.clear(); int volume_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) continue; ++volume_id; // This mesh does not account for the possible Z up SLA offset. - const TriangleMesh* mesh = &mv->mesh(); + const TriangleMesh *mesh = &mv->mesh(); m_triangle_selectors.emplace_back(std::make_unique(*mesh)); // Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize(). @@ -420,15 +505,13 @@ void GLGizmoFdmSupports::update_from_model_object() } } - - PainterGizmoType GLGizmoFdmSupports::get_painter_type() const { return PainterGizmoType::FDM_SUPPORTS; } wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const -{ + { wxString action_name; if (shift_down) action_name = _L("Remove selection"); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 33d84bf753..55bd4395f9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -3,8 +3,6 @@ #include "GLGizmoPainterBase.hpp" -#include "Experiment.cpp" - namespace Slic3r::GUI { class GLGizmoFdmSupports : public GLGizmoPainterBase @@ -38,9 +36,16 @@ private: void select_facets_by_angle(float threshold, bool block); + void compute_smart_support_placement(float limit_angle_deg, float patch_size, float patch_spacing, float islands_tolerance); + // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + + float m_smart_support_limit_angle_deg = 35.0f; + float m_smart_support_patch_size = 6.0f; + float m_smart_support_patch_spacing = 6.0f; + float m_smart_support_islands_tolerance = 1.0f; }; diff --git a/src/slic3r/GUI/Jobs/FDMSupportSpotsJob.cpp b/src/slic3r/GUI/Jobs/FDMSupportSpotsJob.cpp new file mode 100644 index 0000000000..057ae667b2 --- /dev/null +++ b/src/slic3r/GUI/Jobs/FDMSupportSpotsJob.cpp @@ -0,0 +1,102 @@ +#include "FDMSupportSpotsJob.hpp" +#include "libslic3r/TriangleSelector.hpp" + +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp" + +namespace Slic3r::GUI { + +FDMSupportSpotsJob::FDMSupportSpotsJob(Plater *plater, + FDMSupportSpotsConfig support_spots_config, + ObjectID model_object_id, + ObjectID model_instance_id, + std::unordered_map model_volumes_data) : + m_plater(plater), m_support_spots_config(support_spots_config), m_model_object_id(model_object_id), m_model_instance_id( + model_instance_id), + m_model_volumes_data(model_volumes_data) +{ +} + +void FDMSupportSpotsJob::process(Ctl &ctl) { + if (ctl.was_canceled()) { + return; + } + + auto status_text_preparing = _u8L("Densifying mesh and preparing search structures"); + auto status_text_computing = _u8L("Computing support placement"); + auto status_text_canceled = _u8L("Support placement computation canceled"); + auto status_text_done = _u8L("Support placement computation finished"); + + int step_size = 100 / (this->m_model_volumes_data.size() * 2); + int status = 0; + for (auto &data : this->m_model_volumes_data) { + if (ctl.was_canceled()) { + ctl.update_status(100, status_text_canceled); + return; + } + ctl.update_status(status, status_text_preparing); + status += step_size; + FDMSupportSpots support_spots_alg { m_support_spots_config, data.second.mesh, data.second.transform }; + if (ctl.was_canceled()) { + ctl.update_status(100, status_text_canceled); + return; + } + ctl.update_status(status, status_text_computing); + status += step_size; + support_spots_alg.find_support_areas(); + std::vector supported_face_indexes { }; + for (const auto &triangle : support_spots_alg.m_triangles) { + if (triangle.supports) { + supported_face_indexes.push_back(triangle.index); + } + } + + this->m_computed_support_data.emplace(data.first, supported_face_indexes); + } + + if (ctl.was_canceled()) { + ctl.update_status(100, status_text_canceled); + return; + } + ctl.update_status(100, status_text_canceled); +} + +void FDMSupportSpotsJob::finalize(bool canceled, std::exception_ptr &exception) { + if (canceled || exception) + return; + + ModelObject *model_object = nullptr; + for (ModelObject *mo : this->m_plater->model().objects) { + if (mo->id() == this->m_model_object_id) { + model_object = mo; + break; + } + } + + if (model_object == nullptr) + return; + + ModelInstance *model_instance = nullptr; + for (ModelInstance *mi : model_object->instances) { + if (mi->id() == this->m_model_instance_id) { + model_instance = mi; + break; + } + } + if (model_instance == nullptr) + return; + + for (ModelVolume *mv : model_object->volumes) { + auto fdm_support_spots_result = this->m_computed_support_data.find(mv->id().id); + if (fdm_support_spots_result != this->m_computed_support_data.end()) { + TriangleSelector selector { mv->mesh() }; + for (size_t face_index : fdm_support_spots_result->second) { + selector.set_facet(face_index, EnforcerBlockerType::ENFORCER); + } + mv->supported_facets.set(selector); + } + } +} + +} diff --git a/src/slic3r/GUI/Jobs/FDMSupportSpotsJob.hpp b/src/slic3r/GUI/Jobs/FDMSupportSpotsJob.hpp new file mode 100644 index 0000000000..c864d0fa1b --- /dev/null +++ b/src/slic3r/GUI/Jobs/FDMSupportSpotsJob.hpp @@ -0,0 +1,41 @@ +#ifndef SRC_SLIC3R_GUI_JOBS_FDMSUPPORTSPOTSJOB_HPP_ +#define SRC_SLIC3R_GUI_JOBS_FDMSUPPORTSPOTSJOB_HPP_ + +#include "Job.hpp" +#include "libslic3r/FDMSupportSpots.hpp" +#include "slic3r/GUI/Plater.hpp" + +namespace Slic3r::GUI { + +struct SupportedMeshData { + indexed_triangle_set mesh; + Transform3d transform; +}; + +class FDMSupportSpotsJob: public Job { + Plater *m_plater; + FDMSupportSpotsConfig m_support_spots_config; + ObjectID m_model_object_id; + ObjectID m_model_instance_id; + //map of model volume ids and corresponding mesh data + std::unordered_map m_model_volumes_data; + + //vector of supported face indexes for each model volume + std::unordered_map> m_computed_support_data; + +public: + FDMSupportSpotsJob(Plater *plater, + FDMSupportSpotsConfig support_spots_config, + ObjectID model_object_id, + ObjectID model_instance_id, + std::unordered_map model_volumes_data); + + void process(Ctl &ctl) override; + + void finalize(bool canceled, std::exception_ptr &exception) override; + +}; + +} + +#endif /* SRC_SLIC3R_GUI_JOBS_FDMSUPPORTSPOTSJOB_HPP_ */