Refactoring - running the support placement in gui worker

debug UI
debug subdivide
This commit is contained in:
PavelMikus 2022-03-23 13:10:20 +01:00
parent d265d781d4
commit 2b9741da68
11 changed files with 950 additions and 467 deletions

View File

@ -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

View File

@ -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<Vec3f> 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<float>::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 &current_index : this->m_triangle_indexes_by_z) {
Triangle &current = this->m_triangles[current_index];
size_t group_id = 0;
float neighbourhood_unsupported_area = 0;
bool visited_neighbour = false;
std::queue<int> neighbours { };
std::set<int> 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<int> 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
}

View File

@ -0,0 +1,73 @@
#ifndef SRC_LIBSLIC3R_FDMSUPPORTSPOTS_HPP_
#define SRC_LIBSLIC3R_FDMSUPPORTSPOTS_HPP_
#include "libslic3r/Model.hpp"
#include <algorithm>
#include <queue>
#include <boost/nowide/cstdio.hpp>
#include <boost/log/trivial.hpp>
#define DEBUG_FILES
#ifdef DEBUG_FILES
#include <boost/nowide/cstdio.hpp>
#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<FDMSupportSpotsImpl::Triangle> m_triangles;
std::vector<size_t> 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_ */

218
src/libslic3r/Subdivide.cpp Normal file
View File

@ -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<std::pair<size_t, size_t>, VerticesSequence>;
struct Edges
{
Vec3f data[3];
Vec3f lengths;
Edges(const Vec3crd &indices, const std::vector<Vec3f> &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<TriangleLengths, TriangleLengths> divide(
int divide_index, float max_length,
std::vector<Vec3f> &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<size_t, size_t> 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<size_t>(floor(length / max_length));
float count_edge_segments = static_cast<float>(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<size_t, size_t> 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<size_t, size_t> 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<Vec3f> &vertices = its.vertices;
result.vertices = vertices; // copy
std::queue<TriangleLengths> 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;
}
}

View File

@ -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_

View File

@ -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

View File

@ -1,353 +0,0 @@
#include "libslic3r/Model.hpp"
#include <algorithm>
#include <queue>
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
#include "libslic3r/TriangleSelector.hpp"
#include <boost/nowide/cstdio.hpp>
#include <boost/log/trivial.hpp>
#define DEBUG_FILES
#ifdef DEBUG_FILES
#include <boost/nowide/cstdio.hpp>
#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<Triangle> triangles;
std::vector<size_t> 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<Vec3f> 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<float>::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 &current_index : triangle_indexes_by_z) {
Triangle &current = triangles[current_index];
size_t group_id = 0;
float neighbourhood_unsupported_area = 0;
bool visited_neighbour = false;
std::queue<int> neighbours { };
std::set<int> 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<int> 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();
}
}

View File

@ -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 <GL/glew.h>
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<std::string, 3>{"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<std::string, 3> { "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<std::string, 3>{"enforce", "block", "remove"})
for (const auto &t : std::array<std::string, 3> { "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<float>().normalized();
Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
Vec3f limit =
(trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().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<size_t, SupportedMeshData> 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<FDMSupportSpotsJob> job = std::make_unique<FDMSupportSpotsJob>(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<TriangleSelectorGUI>(*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");

View File

@ -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<std::string, wxString> 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;
};

View File

@ -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<size_t, SupportedMeshData> 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<size_t> 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);
}
}
}
}

View File

@ -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<size_t, SupportedMeshData> m_model_volumes_data;
//vector of supported face indexes for each model volume
std::unordered_map<size_t, std::vector<size_t>> m_computed_support_data;
public:
FDMSupportSpotsJob(Plater *plater,
FDMSupportSpotsConfig support_spots_config,
ObjectID model_object_id,
ObjectID model_instance_id,
std::unordered_map<size_t, SupportedMeshData> model_volumes_data);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &exception) override;
};
}
#endif /* SRC_SLIC3R_GUI_JOBS_FDMSUPPORTSPOTSJOB_HPP_ */