mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 05:05:56 +08:00
Merge branch 'lh_mm_gizmo'
This commit is contained in:
commit
7cc94a31e5
@ -7945,7 +7945,7 @@ mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive* pZip, mz_uint file
|
||||
while (p_nf + 4 < e) {
|
||||
mz_uint16 len = ((mz_uint16)p_nf[2]) | ((mz_uint16)p_nf[3] << 8);
|
||||
if (p_nf[0] == '\x75' && p_nf[1] == '\x70' && len >= 5 && p_nf + 4 + len < e && p_nf[4] == '\x01') {
|
||||
mz_uint length = MZ_MIN(len - 5, extra_buf_size - 1);
|
||||
mz_uint length = MZ_MIN((mz_uint)len - 5, extra_buf_size - 1);
|
||||
memcpy(buffer, p_nf + 9, length);
|
||||
return length;
|
||||
}
|
||||
|
@ -241,27 +241,21 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
|
||||
|
||||
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&cursor, TriangleStateType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
assert(this->is_original_triangle(facet_start));
|
||||
|
||||
// Save current cursor center, squared radius and camera direction, so we don't
|
||||
// have to pass it around.
|
||||
m_cursor = std::move(cursor);
|
||||
|
||||
// In case user changed cursor size since last time, update triangle edge limit.
|
||||
// It is necessary to compare the internal radius in m_cursor! radius is in
|
||||
// world coords and does not change after scaling.
|
||||
if (m_old_cursor_radius_sqr != m_cursor->radius_sqr) {
|
||||
set_edge_limit(std::sqrt(m_cursor->radius_sqr) / 5.f);
|
||||
m_old_cursor_radius_sqr = m_cursor->radius_sqr;
|
||||
}
|
||||
// In case user changed cursor parameters size since last time, update triangle edge limit.
|
||||
set_edge_limit(m_cursor->get_edge_limit());
|
||||
|
||||
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
||||
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
|
||||
|
||||
// Now start with the facet the pointer points to and check all adjacent facets.
|
||||
std::vector<int> facets_to_check;
|
||||
std::vector<int> facets_to_check = m_cursor->get_facets_to_select(facet_start, m_vertices, m_triangles, m_orig_size_vertices, m_orig_size_indices);
|
||||
facets_to_check.reserve(16);
|
||||
facets_to_check.emplace_back(facet_start);
|
||||
// Keep track of facets of the original mesh we already processed.
|
||||
std::vector<bool> visited(m_orig_size_indices, false);
|
||||
// Breadth-first search around the hit point. facets_to_check may grow significantly large.
|
||||
@ -292,14 +286,28 @@ bool TriangleSelector::is_facet_clipped(int facet_idx, const ClippingPlane &clp)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TriangleSelector::is_any_neighbor_selected_by_seed_fill(const Triangle &triangle) {
|
||||
size_t triangle_idx = &triangle - m_triangles.data();
|
||||
assert(triangle_idx < m_triangles.size());
|
||||
|
||||
for (int neighbor_idx: m_neighbors[triangle_idx]) {
|
||||
assert(neighbor_idx >= -1);
|
||||
|
||||
if (neighbor_idx >= 0 && m_triangles[neighbor_idx].is_selected_by_seed_fill())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, const Transform3d& trafo_no_translate,
|
||||
const ClippingPlane &clp, float seed_fill_angle, float highlight_by_angle_deg,
|
||||
bool force_reselection)
|
||||
const ClippingPlane &clp, float seed_fill_angle, float seed_fill_gap_area,
|
||||
float highlight_by_angle_deg, ForceReselection force_reselection)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
assert(this->is_original_triangle(facet_start));
|
||||
|
||||
// Recompute seed fill only if the cursor is pointing on facet unselected by seed fill or a clipping plane is active.
|
||||
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active())
|
||||
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active())
|
||||
return;
|
||||
|
||||
this->seed_fill_unselect_all_triangles();
|
||||
@ -308,10 +316,13 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
|
||||
std::queue<int> facet_queue;
|
||||
facet_queue.push(facet_start);
|
||||
|
||||
const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON;
|
||||
const float facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON;
|
||||
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
||||
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
|
||||
|
||||
// Facets that need to be checked for gap filling.
|
||||
std::vector<int> gap_fill_candidate_facets;
|
||||
|
||||
// Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor.
|
||||
while (!facet_queue.empty()) {
|
||||
int current_facet = facet_queue.front();
|
||||
@ -323,14 +334,15 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
|
||||
for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) {
|
||||
assert(split_triangle_idx < int(m_triangles[current_facet].children.size()));
|
||||
assert(m_triangles[current_facet].children[split_triangle_idx] < int(m_triangles.size()));
|
||||
if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child])
|
||||
if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child]) {
|
||||
// Child triangle shares normal with its parent. Select it.
|
||||
facet_queue.push(child);
|
||||
}
|
||||
}
|
||||
} else
|
||||
m_triangles[current_facet].select_by_seed_fill();
|
||||
|
||||
if (current_facet < m_orig_size_indices)
|
||||
if (this->is_original_triangle(current_facet)) {
|
||||
// Propagate over the original triangles.
|
||||
for (int neighbor_idx : m_neighbors[current_facet]) {
|
||||
assert(neighbor_idx >= -1);
|
||||
@ -338,13 +350,102 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
|
||||
// Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do.
|
||||
const Vec3f &n1 = m_face_normals[m_triangles[neighbor_idx].source_triangle];
|
||||
const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle];
|
||||
if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit)
|
||||
if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) {
|
||||
facet_queue.push(neighbor_idx);
|
||||
} else if (seed_fill_gap_area > 0. && get_triangle_area(m_triangles[neighbor_idx]) <= seed_fill_gap_area) {
|
||||
gap_fill_candidate_facets.emplace_back(neighbor_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visited[current_facet] = true;
|
||||
}
|
||||
|
||||
seed_fill_fill_gaps(gap_fill_candidate_facets, seed_fill_gap_area);
|
||||
}
|
||||
|
||||
void TriangleSelector::seed_fill_fill_gaps(const std::vector<int> &gap_fill_candidate_facets, const float seed_fill_gap_area) {
|
||||
std::vector<bool> visited(m_triangles.size(), false);
|
||||
|
||||
for (const int starting_facet_idx: gap_fill_candidate_facets) {
|
||||
const Triangle &starting_facet = m_triangles[starting_facet_idx];
|
||||
|
||||
// If starting_facet_idx was visited from any facet, then we can skip it.
|
||||
if (visited[starting_facet_idx])
|
||||
continue;
|
||||
|
||||
// In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met.
|
||||
// But both of those conditions are here to allow more general usage of this method.
|
||||
if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) {
|
||||
// Already selected by seed fill or split facet, so no additional actions are required.
|
||||
visited[starting_facet_idx] = true;
|
||||
continue;
|
||||
} else if (!is_any_neighbor_selected_by_seed_fill(starting_facet)) {
|
||||
// No neighbor triangles are selected by seed fill, so we will skip them for now.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we have a triangle that has at least one neighbor selected by seed fill.
|
||||
// So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check
|
||||
// if the total area of unselected triangles by seed fill meets the threshold.
|
||||
double total_gap_area = 0.;
|
||||
std::queue<int> facet_queue;
|
||||
std::vector<int> gap_facets;
|
||||
|
||||
facet_queue.push(starting_facet_idx);
|
||||
while (!facet_queue.empty()) {
|
||||
const int current_facet_idx = facet_queue.front();
|
||||
const Triangle ¤t_facet = m_triangles[current_facet_idx];
|
||||
facet_queue.pop();
|
||||
|
||||
if (visited[current_facet_idx])
|
||||
continue;
|
||||
|
||||
if (this->is_original_triangle(current_facet_idx))
|
||||
total_gap_area += get_triangle_area(current_facet);
|
||||
|
||||
// We exceed maximum gap area.
|
||||
if (total_gap_area > seed_fill_gap_area) {
|
||||
// It is necessary to set every facet inside gap_facets unvisited.
|
||||
// Otherwise, we incorrectly select facets that are in a gap that is bigger
|
||||
// than seed_fill_gap_area.
|
||||
for (const int gap_facet_idx : gap_facets)
|
||||
visited[gap_facet_idx] = false;
|
||||
|
||||
gap_facets.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
if (current_facet.is_split()) {
|
||||
for (int split_triangle_idx = 0; split_triangle_idx <= current_facet.number_of_split_sides(); ++split_triangle_idx) {
|
||||
assert(split_triangle_idx < int(current_facet.children.size()));
|
||||
assert(current_facet.children[split_triangle_idx] < int(m_triangles.size()));
|
||||
if (int child = current_facet.children[split_triangle_idx]; !visited[child])
|
||||
facet_queue.push(child);
|
||||
}
|
||||
} else if (total_gap_area < seed_fill_gap_area) {
|
||||
gap_facets.emplace_back(current_facet_idx);
|
||||
}
|
||||
|
||||
if (this->is_original_triangle(current_facet_idx)) {
|
||||
// Propagate over the original triangles.
|
||||
for (int neighbor_idx: m_neighbors[current_facet_idx]) {
|
||||
assert(neighbor_idx >= -1);
|
||||
if (neighbor_idx >= 0 && !visited[neighbor_idx] && !m_triangles[neighbor_idx].is_selected_by_seed_fill())
|
||||
facet_queue.push(neighbor_idx);
|
||||
}
|
||||
}
|
||||
|
||||
visited[current_facet_idx] = true;
|
||||
}
|
||||
|
||||
for (int to_seed_idx : gap_facets)
|
||||
m_triangles[to_seed_idx].select_by_seed_fill();
|
||||
|
||||
gap_facets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleSelector::precompute_all_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out, std::vector<Vec3i> &neighbors_propagated_out) const
|
||||
@ -451,43 +552,59 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver
|
||||
process_subtriangle(touching.second, Partition::Second);
|
||||
}
|
||||
|
||||
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, const ClippingPlane &clp, bool propagate, bool force_reselection)
|
||||
{
|
||||
// Returns all triangles that are touching the given facet.
|
||||
std::vector<int> TriangleSelector::get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const {
|
||||
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
|
||||
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
|
||||
|
||||
const Vec3i vertices = { m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2] };
|
||||
|
||||
std::vector<int> touching_triangles;
|
||||
append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles);
|
||||
append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles);
|
||||
append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles);
|
||||
|
||||
for (int neighbor_idx: neighbors_propagated) {
|
||||
if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split())
|
||||
touching_triangles.emplace_back(neighbor_idx);
|
||||
}
|
||||
|
||||
return touching_triangles;
|
||||
}
|
||||
|
||||
void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_start, const ClippingPlane &clp,
|
||||
float bucket_fill_angle, float bucket_fill_gap_area,
|
||||
BucketFillPropagate propagate, ForceReselection force_reselection) {
|
||||
int start_facet_idx = select_unsplit_triangle(hit, facet_start);
|
||||
assert(start_facet_idx != -1);
|
||||
// Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill or a clipping plane is active.
|
||||
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active()))
|
||||
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active()))
|
||||
return;
|
||||
|
||||
assert(!m_triangles[start_facet_idx].is_split());
|
||||
TriangleStateType start_facet_state = m_triangles[start_facet_idx].get_state();
|
||||
this->seed_fill_unselect_all_triangles();
|
||||
|
||||
if (!propagate) {
|
||||
if (propagate == BucketFillPropagate::NO) {
|
||||
if (m_triangle_selected_by_seed_fill != -1)
|
||||
this->seed_fill_unselect_triangle(m_triangle_selected_by_seed_fill);
|
||||
|
||||
m_triangles[start_facet_idx].select_by_seed_fill();
|
||||
m_triangle_selected_by_seed_fill = start_facet_idx;
|
||||
return;
|
||||
} else {
|
||||
m_triangle_selected_by_seed_fill = -1;
|
||||
this->seed_fill_unselect_all_triangles();
|
||||
}
|
||||
|
||||
auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector<int> {
|
||||
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
|
||||
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
|
||||
std::vector<int> touching_triangles;
|
||||
Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]};
|
||||
append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles);
|
||||
append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles);
|
||||
append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles);
|
||||
|
||||
for (int neighbor_idx : neighbors_propagated)
|
||||
if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split())
|
||||
touching_triangles.emplace_back(neighbor_idx);
|
||||
|
||||
return touching_triangles;
|
||||
};
|
||||
const float facet_angle_limit = std::cos(Geometry::deg2rad(bucket_fill_angle)) - EPSILON;
|
||||
|
||||
auto [neighbors, neighbors_propagated] = this->precompute_all_neighbors();
|
||||
std::vector<bool> visited(m_triangles.size(), false);
|
||||
std::queue<int> facet_queue;
|
||||
|
||||
// Facets that need to be checked for gap filling.
|
||||
std::vector<int> gap_fill_candidate_facets;
|
||||
|
||||
facet_queue.push(start_facet_idx);
|
||||
while (!facet_queue.empty()) {
|
||||
int current_facet = facet_queue.front();
|
||||
@ -497,18 +614,98 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_
|
||||
if (!visited[current_facet]) {
|
||||
m_triangles[current_facet].select_by_seed_fill();
|
||||
|
||||
std::vector<int> touching_triangles = get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]);
|
||||
for(const int tr_idx : touching_triangles) {
|
||||
std::vector<int> touching_triangles = this->get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]);
|
||||
for (const int tr_idx: touching_triangles) {
|
||||
if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || is_facet_clipped(tr_idx, clp))
|
||||
continue;
|
||||
|
||||
assert(!m_triangles[tr_idx].is_split());
|
||||
facet_queue.push(tr_idx);
|
||||
// Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do.
|
||||
const Vec3f &n1 = m_face_normals[m_triangles[tr_idx].source_triangle];
|
||||
const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle];
|
||||
if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) {
|
||||
assert(!m_triangles[tr_idx].is_split());
|
||||
facet_queue.push(tr_idx);
|
||||
} else if (bucket_fill_gap_area > 0. && get_triangle_area(m_triangles[tr_idx]) <= bucket_fill_gap_area) {
|
||||
gap_fill_candidate_facets.emplace_back(tr_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visited[current_facet] = true;
|
||||
}
|
||||
|
||||
bucket_fill_fill_gaps(gap_fill_candidate_facets, bucket_fill_gap_area, start_facet_state, neighbors, neighbors_propagated);
|
||||
}
|
||||
|
||||
void TriangleSelector::bucket_fill_fill_gaps(const std::vector<int> &gap_fill_candidate_facets, const float bucket_fill_gap_area,
|
||||
const TriangleStateType start_facet_state, const std::vector<Vec3i> &neighbors,
|
||||
const std::vector<Vec3i> &neighbors_propagated) {
|
||||
std::vector<bool> visited(m_triangles.size(), false);
|
||||
|
||||
for (const int starting_facet_idx: gap_fill_candidate_facets) {
|
||||
const Triangle &starting_facet = m_triangles[starting_facet_idx];
|
||||
|
||||
// If starting_facet_idx was visited from any facet, then we can skip it.
|
||||
if (visited[starting_facet_idx])
|
||||
continue;
|
||||
|
||||
// In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met.
|
||||
// But both of those conditions are here to allow more general usage of this method.
|
||||
if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) {
|
||||
// Already selected by seed fill or split facet, so no additional actions are required.
|
||||
visited[starting_facet_idx] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// In the way how bucket_fill_select_triangles() is implemented, all gap_fill_candidate_facets
|
||||
// have at least one neighbor selected by seed fill.
|
||||
// So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check
|
||||
// if the total area of unselected triangles by seed fill meets the threshold.
|
||||
double total_gap_area = 0.;
|
||||
std::queue<int> facet_queue;
|
||||
std::vector<int> gap_facets;
|
||||
|
||||
facet_queue.push(starting_facet_idx);
|
||||
while (!facet_queue.empty()) {
|
||||
const int current_facet_idx = facet_queue.front();
|
||||
const Triangle ¤t_facet = m_triangles[current_facet_idx];
|
||||
facet_queue.pop();
|
||||
|
||||
if (visited[current_facet_idx])
|
||||
continue;
|
||||
|
||||
total_gap_area += get_triangle_area(current_facet);
|
||||
|
||||
// We exceed maximum gap area.
|
||||
if (total_gap_area > bucket_fill_gap_area) {
|
||||
// It is necessary to set every facet inside gap_facets unvisited.
|
||||
// Otherwise, we incorrectly select facets that are in a gap that is bigger
|
||||
// than bucket_fill_gap_area.
|
||||
for (const int gap_facet_idx : gap_facets)
|
||||
visited[gap_facet_idx] = false;
|
||||
|
||||
gap_facets.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
gap_facets.emplace_back(current_facet_idx);
|
||||
|
||||
std::vector<int> touching_triangles = this->get_all_touching_triangles(current_facet_idx, neighbors[current_facet_idx], neighbors_propagated[current_facet_idx]);
|
||||
for (const int tr_idx: touching_triangles) {
|
||||
if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || m_triangles[tr_idx].is_selected_by_seed_fill())
|
||||
continue;
|
||||
|
||||
facet_queue.push(tr_idx);
|
||||
}
|
||||
|
||||
visited[current_facet_idx] = true;
|
||||
}
|
||||
|
||||
for (int to_seed_idx : gap_facets)
|
||||
m_triangles[to_seed_idx].select_by_seed_fill();
|
||||
|
||||
gap_facets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Selects either the whole triangle (discarding any children it had), or divides
|
||||
@ -878,7 +1075,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei
|
||||
|
||||
if (num_of_inside_vertices == 0
|
||||
&& ! m_cursor->is_pointer_in_triangle(*tr, m_vertices)
|
||||
&& ! m_cursor->is_edge_inside_cursor(*tr, m_vertices))
|
||||
&& ! m_cursor->is_any_edge_inside_cursor(*tr, m_vertices))
|
||||
return false;
|
||||
|
||||
if (num_of_inside_vertices == 3) {
|
||||
@ -918,7 +1115,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei
|
||||
}
|
||||
|
||||
void TriangleSelector::set_facet(int facet_idx, TriangleStateType state) {
|
||||
assert(facet_idx < m_orig_size_indices);
|
||||
assert(this->is_original_triangle(facet_idx));
|
||||
undivide_triangle(facet_idx);
|
||||
assert(! m_triangles[facet_idx].is_split());
|
||||
m_triangles[facet_idx].set_state(state);
|
||||
@ -949,8 +1146,8 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
|
||||
// In case the object is non-uniformly scaled, transform the
|
||||
// points to world coords.
|
||||
if (! m_cursor->uniform_scaling) {
|
||||
for (size_t i=0; i<pts.size(); ++i) {
|
||||
if (!m_cursor->uniform_scaling) {
|
||||
for (size_t i = 0; i < pts.size(); ++i) {
|
||||
pts_transformed[i] = m_cursor->trafo * (*pts[i]);
|
||||
pts[i] = &pts_transformed[i];
|
||||
}
|
||||
@ -1011,16 +1208,21 @@ int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vec
|
||||
return inside;
|
||||
}
|
||||
|
||||
// Is any edge inside Sphere cursor?
|
||||
bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
inline std::array<Vec3f, 3> TriangleSelector::Cursor::transform_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const {
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
|
||||
// Is any edge inside Sphere cursor?
|
||||
bool TriangleSelector::Sphere::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
@ -1031,16 +1233,10 @@ bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const s
|
||||
}
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
bool TriangleSelector::Circle::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f &p = this->center;
|
||||
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
|
||||
const Vec3f &p = this->center;
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &a = pts[side];
|
||||
const Vec3f &b = pts[side < 2 ? side + 1 : 0];
|
||||
@ -1096,42 +1292,43 @@ void TriangleSelector::undivide_triangle(int facet_idx)
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleSelector::remove_useless_children(int facet_idx)
|
||||
{
|
||||
// Returns true when some triangle during recursive descending was removed (undivided).
|
||||
bool TriangleSelector::remove_useless_children(int facet_idx) {
|
||||
// Check that all children are leafs of the same type. If not, try to
|
||||
// make them (recursive call). Remove them if sucessful.
|
||||
// make them (recursive call). Remove them if successful.
|
||||
|
||||
assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid());
|
||||
Triangle& tr = m_triangles[facet_idx];
|
||||
Triangle &tr = m_triangles[facet_idx];
|
||||
|
||||
if (! tr.is_split()) {
|
||||
if (!tr.is_split()) {
|
||||
// This is a leaf, there nothing to do. This can happen during the
|
||||
// first (non-recursive call). Shouldn't otherwise.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Call this for all non-leaf children.
|
||||
for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) {
|
||||
bool children_removed = false;
|
||||
for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) {
|
||||
assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid());
|
||||
if (m_triangles[tr.children[child_idx]].is_split())
|
||||
remove_useless_children(tr.children[child_idx]);
|
||||
children_removed |= remove_useless_children(tr.children[child_idx]);
|
||||
}
|
||||
|
||||
|
||||
// Return if a child is not leaf or two children differ in type.
|
||||
TriangleStateType first_child_type = TriangleStateType::NONE;
|
||||
for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) {
|
||||
for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) {
|
||||
if (m_triangles[tr.children[child_idx]].is_split())
|
||||
return;
|
||||
return children_removed;
|
||||
if (child_idx == 0)
|
||||
first_child_type = m_triangles[tr.children[0]].get_state();
|
||||
else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type)
|
||||
return;
|
||||
return children_removed;
|
||||
}
|
||||
|
||||
// If we got here, the children can be removed.
|
||||
undivide_triangle(facet_idx);
|
||||
tr.set_state(first_child_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TriangleSelector::garbage_collect()
|
||||
@ -1216,7 +1413,7 @@ void TriangleSelector::reset()
|
||||
|
||||
void TriangleSelector::set_edge_limit(float edge_limit)
|
||||
{
|
||||
m_edge_limit_sqr = std::pow(edge_limit, 2.f);
|
||||
m_edge_limit_sqr = Slic3r::sqr(edge_limit);
|
||||
}
|
||||
|
||||
int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const TriangleStateType state) {
|
||||
@ -1860,7 +2057,22 @@ void TriangleSelector::seed_fill_unselect_all_triangles()
|
||||
triangle.unselect_by_seed_fill();
|
||||
}
|
||||
|
||||
void TriangleSelector::seed_fill_unselect_triangle(const int facet_idx) {
|
||||
assert(facet_idx > 0 && facet_idx < m_triangles.size());
|
||||
Triangle &triangle = m_triangles[facet_idx];
|
||||
|
||||
assert(!triangle.is_split());
|
||||
if (!triangle.is_split())
|
||||
triangle.unselect_by_seed_fill();
|
||||
}
|
||||
|
||||
void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) {
|
||||
if (m_triangle_selected_by_seed_fill != -1) {
|
||||
this->seed_fill_apply_on_single_triangle(new_state, m_triangle_selected_by_seed_fill);
|
||||
m_triangle_selected_by_seed_fill = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
for (Triangle &triangle : m_triangles)
|
||||
if (!triangle.is_split() && triangle.is_selected_by_seed_fill())
|
||||
triangle.set_state(new_state);
|
||||
@ -1872,13 +2084,29 @@ void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state)
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleSelector::seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx) {
|
||||
assert(facet_idx > 0 && facet_idx < m_triangles.size());
|
||||
|
||||
if (Triangle &triangle = m_triangles[facet_idx]; !triangle.is_split() && triangle.is_selected_by_seed_fill()) {
|
||||
triangle.set_state(new_state);
|
||||
remove_useless_children(triangle.source_triangle);
|
||||
}
|
||||
}
|
||||
|
||||
double TriangleSelector::get_triangle_area(const Triangle &triangle) const {
|
||||
const stl_vertex &v0 = m_vertices[triangle.verts_idxs[0]].v;
|
||||
const stl_vertex &v1 = m_vertices[triangle.verts_idxs[1]].v;
|
||||
const stl_vertex &v2 = m_vertices[triangle.verts_idxs[2]].v;
|
||||
return (v1 - v0).cross(v2 - v0).norm() / 2.;
|
||||
}
|
||||
|
||||
TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: source{source_}, trafo{trafo_.cast<float>()}, clipping_plane{clipping_plane_}
|
||||
{
|
||||
Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor();
|
||||
if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) {
|
||||
radius = float(radius_world / sf(0));
|
||||
radius_sqr = float(Slic3r::sqr(radius_world / sf(0)));
|
||||
if (is_approx(sf.x(), sf.y()) && is_approx(sf.y(), sf.z())) {
|
||||
radius = float(radius_world / sf.x());
|
||||
radius_sqr = float(Slic3r::sqr(radius_world / sf.x()));
|
||||
uniform_scaling = true;
|
||||
} else {
|
||||
// In case that the transformation is non-uniform, all checks whether
|
||||
@ -1890,6 +2118,8 @@ TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const
|
||||
radius_sqr = Slic3r::sqr(radius_world);
|
||||
trafo_normal = trafo.linear().inverse().transpose();
|
||||
}
|
||||
|
||||
m_edge_limit = std::sqrt(radius_sqr) / 5.f;
|
||||
}
|
||||
|
||||
TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
|
||||
@ -2048,15 +2278,9 @@ bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
bool TriangleSelector::Capsule3D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
@ -2068,15 +2292,8 @@ bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, cons
|
||||
}
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
bool TriangleSelector::Capsule2D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
// Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center.
|
||||
const Vec3f rectangle_da_dir = centers_diff.cross(this->dir);
|
||||
@ -2095,6 +2312,7 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons
|
||||
return false;
|
||||
};
|
||||
|
||||
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
@ -2122,4 +2340,54 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons
|
||||
return false;
|
||||
}
|
||||
|
||||
TriangleSelector::HeightRange::HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane)
|
||||
: Cursor(Vec3f::Zero(), 0.f, trafo, clipping_plane) {
|
||||
m_z_range_top = std::max(mesh_hit.z() + z_range / 2.f, float(mesh_bbox.min.z()));
|
||||
m_z_range_bottom = std::min(mesh_hit.z() - z_range / 2.f, float(mesh_bbox.max.z()));
|
||||
m_edge_limit = 0.1f;
|
||||
}
|
||||
|
||||
bool TriangleSelector::HeightRange::is_mesh_point_inside(const Vec3f &point) const {
|
||||
const float transformed_point_z = (this->uniform_scaling ? point : Vec3f(this->trafo * point)).z();
|
||||
return Slic3r::is_in_range<float>(transformed_point_z, m_z_range_bottom, m_z_range_top);
|
||||
}
|
||||
|
||||
bool TriangleSelector::HeightRange::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const {
|
||||
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
|
||||
// If all vertices are below m_z_range_bottom or all vertices are above m_z_range_top, then it means that no edge
|
||||
// is inside the height range. Otherwise, there is at least one edge inside the height range.
|
||||
return !((pts[0].z() < m_z_range_bottom && pts[1].z() < m_z_range_bottom && pts[2].z() < m_z_range_bottom) ||
|
||||
(pts[0].z() > m_z_range_top && pts[1].z() > m_z_range_top && pts[2].z() > m_z_range_top));
|
||||
}
|
||||
|
||||
std::vector<int> TriangleSelector::HeightRange::get_facets_to_select(const int facet_idx, const std::vector<Vertex> &vertices, const std::vector<Triangle> &triangles, const int orig_size_vertices, const int orig_size_indices) const {
|
||||
std::vector<int> facets_to_check;
|
||||
|
||||
// Assigns each vertex a value of -1, 1, or 0. The value -1 indicates a vertex is below m_z_range_bottom,
|
||||
// while 1 indicates a vertex is above m_z_range_top. The value of 0 indicates that the vertex between
|
||||
// m_z_range_bottom and m_z_range_top.
|
||||
std::vector<int8_t> vertex_side(orig_size_vertices, 0);
|
||||
if (trafo.matrix() == Transform3f::Identity().matrix()) {
|
||||
for (int i = 0; i < orig_size_vertices; ++i) {
|
||||
const float z = vertices[i].v.z();
|
||||
vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < orig_size_vertices; ++i) {
|
||||
const float z = (this->uniform_scaling ? vertices[i].v : Vec3f(this->trafo * vertices[i].v)).z();
|
||||
vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if each triangle crosses m_z_range_bottom or m_z_range_top.
|
||||
for (int i = 0; i < orig_size_indices; ++i) {
|
||||
const std::array<int, 3> &face = triangles[i].verts_idxs;
|
||||
const std::array<int8_t, 3> sides = { vertex_side[face[0]], vertex_side[face[1]], vertex_side[face[2]] };
|
||||
if ((sides[0] * sides[1] <= 0) || (sides[1] * sides[2] <= 0) || (sides[0] * sides[2] <= 0))
|
||||
facets_to_check.emplace_back(i);
|
||||
}
|
||||
|
||||
return facets_to_check;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -67,10 +67,21 @@ protected:
|
||||
struct Vertex;
|
||||
|
||||
public:
|
||||
enum CursorType {
|
||||
enum class CursorType {
|
||||
CIRCLE,
|
||||
SPHERE,
|
||||
POINTER
|
||||
POINTER,
|
||||
HEIGHT_RANGE
|
||||
};
|
||||
|
||||
enum class ForceReselection {
|
||||
NO,
|
||||
YES
|
||||
};
|
||||
|
||||
enum class BucketFillPropagate {
|
||||
NO,
|
||||
YES
|
||||
};
|
||||
|
||||
struct ClippingPlane
|
||||
@ -91,14 +102,21 @@ public:
|
||||
Cursor() = delete;
|
||||
virtual ~Cursor() = default;
|
||||
|
||||
float get_edge_limit() { return m_edge_limit; };
|
||||
|
||||
bool is_pointer_in_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
std::array<Vec3f, 3> transform_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
|
||||
virtual bool is_mesh_point_inside(const Vec3f &point) const = 0;
|
||||
virtual bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const = 0;
|
||||
virtual int vertices_inside(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const = 0;
|
||||
virtual bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const = 0;
|
||||
virtual bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const = 0;
|
||||
|
||||
virtual std::vector<int> get_facets_to_select(int facet_idx, const std::vector<Vertex> &vertices, const std::vector<Triangle> &triangles, int orig_size_vertices, int orig_size_indices) const {
|
||||
return { facet_idx };
|
||||
};
|
||||
|
||||
static bool is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector<Vec3f> &face_normals);
|
||||
|
||||
protected:
|
||||
@ -115,6 +133,8 @@ public:
|
||||
|
||||
ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only
|
||||
|
||||
float m_edge_limit;
|
||||
|
||||
friend TriangleSelector;
|
||||
};
|
||||
|
||||
@ -174,7 +194,7 @@ public:
|
||||
~Sphere() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
};
|
||||
|
||||
@ -187,9 +207,8 @@ public:
|
||||
~Circle() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
|
||||
{
|
||||
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override {
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
};
|
||||
@ -204,7 +223,7 @@ public:
|
||||
~Capsule3D() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
};
|
||||
|
||||
@ -218,13 +237,32 @@ public:
|
||||
~Capsule2D() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
|
||||
{
|
||||
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override {
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
};
|
||||
|
||||
class HeightRange : public Cursor
|
||||
{
|
||||
public:
|
||||
HeightRange() = delete;
|
||||
|
||||
explicit HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane);
|
||||
~HeightRange() override = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override { return false; }
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
|
||||
std::vector<int> get_facets_to_select(int facet_idx, const std::vector<Vertex> &vertices, const std::vector<Triangle> &triangles, int orig_size_vertices, int orig_size_indices) const override;
|
||||
|
||||
private:
|
||||
float m_z_range_top;
|
||||
float m_z_range_bottom;
|
||||
};
|
||||
|
||||
struct TriangleBitStreamMapping
|
||||
{
|
||||
// Index of the triangle to which we assign the bitstream containing splitting information.
|
||||
@ -298,19 +336,22 @@ public:
|
||||
bool triangle_splitting, // If triangles will be split base on the cursor or not
|
||||
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
|
||||
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
|
||||
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
|
||||
float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
|
||||
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
|
||||
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
|
||||
float seed_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected.
|
||||
float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
|
||||
|
||||
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
|
||||
bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
|
||||
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
|
||||
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
|
||||
float bucket_fill_angle, // the maximal angle between two facets to be painted by the same color
|
||||
float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected.
|
||||
BucketFillPropagate propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
|
||||
ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
|
||||
|
||||
bool has_facets(TriangleStateType state) const;
|
||||
static bool has_facets(const TriangleSplittingData &data, TriangleStateType test_state);
|
||||
@ -360,10 +401,20 @@ public:
|
||||
// For all triangles, remove the flag indicating that the triangle was selected by seed fill.
|
||||
void seed_fill_unselect_all_triangles();
|
||||
|
||||
// Remove the flag indicating that the triangle was selected by seed fill.
|
||||
void seed_fill_unselect_triangle(int facet_idx);
|
||||
|
||||
// For all triangles selected by seed fill, set new TriangleStateType and remove flag indicating that triangle was selected by seed fill.
|
||||
// The operation may merge split triangles if they are being assigned the same color.
|
||||
void seed_fill_apply_on_triangles(TriangleStateType new_state);
|
||||
|
||||
// For the triangle selected by seed fill, set a new TriangleStateType and remove the flag indicating that the triangle was selected by seed fill.
|
||||
// The operation may merge a split triangle if it is being assigned the same color.
|
||||
void seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx);
|
||||
|
||||
// Compute total area of the triangle.
|
||||
double get_triangle_area(const Triangle &triangle) const;
|
||||
|
||||
protected:
|
||||
// Triangle and info about how it's split.
|
||||
class Triangle {
|
||||
@ -450,8 +501,9 @@ protected:
|
||||
int m_orig_size_indices = 0;
|
||||
|
||||
std::unique_ptr<Cursor> m_cursor;
|
||||
// Zero indicates an uninitialized state.
|
||||
float m_old_cursor_radius_sqr = 0;
|
||||
|
||||
// Single triangle selected by seed fill. It is used to optimize painting using a single triangle brush.
|
||||
int m_triangle_selected_by_seed_fill = -1;
|
||||
|
||||
// Private functions:
|
||||
private:
|
||||
@ -459,7 +511,7 @@ private:
|
||||
bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting);
|
||||
void undivide_triangle(int facet_idx);
|
||||
void split_triangle(int facet_idx, const Vec3i &neighbors);
|
||||
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
|
||||
bool remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
|
||||
bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const;
|
||||
int push_triangle(int a, int b, int c, int source_triangle, TriangleStateType state = TriangleStateType::NONE);
|
||||
void perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state);
|
||||
@ -483,6 +535,12 @@ private:
|
||||
void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector<int> &touching_subtriangles_out) const;
|
||||
void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const;
|
||||
|
||||
// Returns all triangles that are touching the given facet.
|
||||
std::vector<int> get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const;
|
||||
|
||||
// Check if the triangle index is the original triangle from mesh, or it was additionally created by splitting.
|
||||
bool is_original_triangle(int triangle_idx) const { return triangle_idx < m_orig_size_indices; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
|
||||
bool verify_triangle_midpoints(const Triangle& tr) const;
|
||||
@ -501,6 +559,17 @@ private:
|
||||
|
||||
void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const;
|
||||
|
||||
bool is_any_neighbor_selected_by_seed_fill(const Triangle &triangle);
|
||||
|
||||
void seed_fill_fill_gaps(const std::vector<int> &gap_fill_candidate_facets, // Facet of the original mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected).
|
||||
float seed_fill_gap_area); // The maximal area that will be automatically selected when the surrounding triangles have already been selected.
|
||||
|
||||
void bucket_fill_fill_gaps(const std::vector<int> &gap_fill_candidate_facets, // Facet of the mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected).
|
||||
float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected.
|
||||
TriangleStateType start_facet_state, // The state of the starting facet that determines which neighbors to consider.
|
||||
const std::vector<Vec3i> &neighbors,
|
||||
const std::vector<Vec3i> &neighbors_propagate);
|
||||
|
||||
int m_free_triangles_head { -1 };
|
||||
int m_free_vertices_head { -1 };
|
||||
};
|
||||
|
@ -487,6 +487,11 @@ Fn for_each_in_tuple(Fn fn, Tup &&tup)
|
||||
return fn;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool is_in_range(const T &value, const T &low, const T &high) {
|
||||
return low <= value && value <= high;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // _libslic3r_h_
|
||||
|
@ -103,7 +103,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
if (! m_c->selection_info()->model_object())
|
||||
return;
|
||||
|
||||
const float approx_height = m_imgui->scaled(25.f);
|
||||
const float approx_height = m_imgui->scaled(26.3f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
@ -115,7 +115,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||
const float smart_fill_slider_left = ImGuiPureWrap::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 = ImGuiPureWrap::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 = ImGuiPureWrap::calc_text_size(m_desc.at("highlight_by_angle"), false, autoset_slider_label_max_width).x + m_imgui->scaled(1.f);
|
||||
|
||||
const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
@ -301,11 +301,12 @@ 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")))
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
@ -459,7 +460,7 @@ void GLGizmoFdmSupports::update_model_object() const
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
++idx;
|
||||
updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get());
|
||||
updated |= mv->supported_facets.set(*m_triangle_selectors[idx]);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
|
@ -110,6 +110,12 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
m_desc["second_color"] = _u8L("Second color");
|
||||
m_desc["remove_caption"] = _u8L("Shift + Left mouse button") + ": ";
|
||||
m_desc["remove"] = _u8L("Remove painted color");
|
||||
|
||||
m_desc["alt_caption"] = _u8L("Alt + Mouse wheel") + ": ";
|
||||
m_desc["alt_brush"] = _u8L("Change brush size");
|
||||
m_desc["alt_fill"] = _u8L("Change angle");
|
||||
m_desc["alt_height_range"] = _u8L("Change height range");
|
||||
|
||||
m_desc["remove_all"] = _u8L("Clear all");
|
||||
m_desc["circle"] = _u8L("Circle");
|
||||
m_desc["sphere"] = _u8L("Sphere");
|
||||
@ -119,10 +125,15 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
m_desc["tool_brush"] = _u8L("Brush");
|
||||
m_desc["tool_smart_fill"] = _u8L("Smart fill");
|
||||
m_desc["tool_bucket_fill"] = _u8L("Bucket fill");
|
||||
m_desc["tool_height_range"] = _u8L("Height range");
|
||||
|
||||
m_desc["smart_fill_angle"] = _u8L("Smart fill angle");
|
||||
m_desc["bucket_fill_angle"] = _u8L("Bucket fill angle");
|
||||
|
||||
m_desc["split_triangles"] = _u8L("Split triangles");
|
||||
|
||||
m_desc["height_range_z_range"] = _u8L("Height range");
|
||||
|
||||
init_extruders_data();
|
||||
|
||||
return true;
|
||||
@ -263,17 +274,19 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
if (!m_c->selection_info()->model_object())
|
||||
return;
|
||||
|
||||
const float approx_height = m_imgui->scaled(22.0f);
|
||||
const float approx_height = m_imgui->scaled(25.35f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
ImGuiPureWrap::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(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x,
|
||||
ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
|
||||
const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x,
|
||||
ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
|
||||
const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||
const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
|
||||
const float bucket_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("bucket_fill_angle")).x + m_imgui->scaled(1.f);
|
||||
const float height_range_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("height_range_z_range")).x + m_imgui->scaled(1.f);
|
||||
|
||||
const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
@ -286,30 +299,39 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
const float combo_label_width = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("first_color")).x,
|
||||
ImGuiPureWrap::calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f);
|
||||
|
||||
const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_height_range = ImGuiPureWrap::calc_text_size(m_desc["tool_height_range"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
const float tool_type_radio_first_line = tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill;
|
||||
const float tool_type_radio_second_line = tool_type_radio_height_range;
|
||||
const float tool_type_radio_max_width = std::max(tool_type_radio_first_line, tool_type_radio_second_line);
|
||||
|
||||
const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
float caption_max = 0.f;
|
||||
float caption_max = 0.f;
|
||||
for (const std::string t : {"first_color", "second_color", "remove", "alt"}) {
|
||||
caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x);
|
||||
}
|
||||
|
||||
float total_text_max = 0.f;
|
||||
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) {
|
||||
caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x);
|
||||
for (const std::string t : {"first_color", "second_color", "remove", "alt_brush", "alt_fill", "alt_height_range"}) {
|
||||
total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x);
|
||||
}
|
||||
|
||||
total_text_max += caption_max + m_imgui->scaled(1.f);
|
||||
caption_max += m_imgui->scaled(1.f);
|
||||
|
||||
const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
|
||||
const float sliders_left_width = std::max({smart_fill_slider_left, bucket_fill_slider_left, cursor_slider_left, clipping_slider_left, height_range_slider_left});
|
||||
const float slider_icon_width = ImGuiPureWrap::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, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
|
||||
window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill);
|
||||
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
|
||||
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, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
|
||||
window_width = std::max(window_width, tool_type_radio_max_width);
|
||||
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
|
||||
|
||||
auto draw_text_with_caption = [&caption_max](const std::string &caption, const std::string& text) {
|
||||
ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_ORANGE_LIGHT, caption);
|
||||
@ -317,8 +339,14 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
ImGuiPureWrap::text(text);
|
||||
};
|
||||
|
||||
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"})
|
||||
for (const std::string t : {"first_color", "second_color", "remove"}) {
|
||||
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
|
||||
}
|
||||
|
||||
std::string alt_hint_text = (m_tool_type == ToolType::BRUSH) ? "alt_brush" :
|
||||
(m_tool_type == ToolType::HEIGHT_RANGE) ? "alt_height_range"
|
||||
: "alt_fill";
|
||||
draw_text_with_caption(m_desc.at("alt_caption"), m_desc.at(alt_hint_text));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
@ -360,8 +388,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
ImGuiPureWrap::text(m_desc.at("tool_type"));
|
||||
ImGui::NewLine();
|
||||
|
||||
float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(1.5f)) / 2.f;
|
||||
ImGui::SameLine(tool_type_offset);
|
||||
const float tool_type_first_line_offset = (window_width - tool_type_radio_first_line + m_imgui->scaled(1.5f)) / 2.f;
|
||||
ImGui::SameLine(tool_type_first_line_offset);
|
||||
ImGui::PushItemWidth(tool_type_radio_brush);
|
||||
if (ImGuiPureWrap::radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) {
|
||||
m_tool_type = ToolType::BRUSH;
|
||||
@ -374,7 +402,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGuiPureWrap::tooltip(_u8L("Paints facets according to the chosen painting brush."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
|
||||
ImGui::SameLine(tool_type_first_line_offset + tool_type_radio_brush);
|
||||
ImGui::PushItemWidth(tool_type_radio_smart_fill);
|
||||
if (ImGuiPureWrap::radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) {
|
||||
m_tool_type = ToolType::SMART_FILL;
|
||||
@ -387,7 +415,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill);
|
||||
ImGui::SameLine(tool_type_first_line_offset + tool_type_radio_brush + tool_type_radio_smart_fill);
|
||||
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
|
||||
if (ImGuiPureWrap::radio_button(m_desc["tool_bucket_fill"], m_tool_type == ToolType::BUCKET_FILL)) {
|
||||
m_tool_type = ToolType::BUCKET_FILL;
|
||||
@ -400,9 +428,25 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets that have the same color."), max_tooltip_width);
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
const float tool_type_second_line_offset = (window_width - tool_type_radio_second_line + m_imgui->scaled(1.5f)) / 2.f;
|
||||
ImGui::SameLine(tool_type_second_line_offset);
|
||||
ImGui::PushItemWidth(tool_type_radio_height_range);
|
||||
if (ImGuiPureWrap::radio_button(m_desc["tool_height_range"], m_tool_type == ToolType::HEIGHT_RANGE)) {
|
||||
m_tool_type = ToolType::HEIGHT_RANGE;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGuiPureWrap::tooltip(_u8L("Paints facets within the chosen height range."), max_tooltip_width);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(m_tool_type == ToolType::BRUSH) {
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
ImGuiPureWrap::text(m_desc.at("cursor_type"));
|
||||
ImGui::NewLine();
|
||||
|
||||
@ -449,18 +493,35 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
m_imgui->disabled_end();
|
||||
|
||||
ImGui::Separator();
|
||||
} else if(m_tool_type == ToolType::SMART_FILL) {
|
||||
} else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":");
|
||||
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
|
||||
"placed after the number with no whitespace in between.");
|
||||
ImGuiPureWrap::text((m_tool_type == ToolType::SMART_FILL ? m_desc["smart_fill_angle"] : m_desc["bucket_fill_angle"]) + ":");
|
||||
std::string format_str_angle = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
|
||||
"placed after the number with no whitespace in between.");
|
||||
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")))
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
float &fill_angle = (m_tool_type == ToolType::SMART_FILL) ? m_smart_fill_angle : m_bucket_fill_angle;
|
||||
if (m_imgui->slider_float("##fill_angle", &fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str_angle.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();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
} else if (m_tool_type == ToolType::HEIGHT_RANGE) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGuiPureWrap::text(m_desc["height_range_z_range"] + ":");
|
||||
std::string format_str_angle = std::string("%.2f ") + I18N::translate_utf8("mm", "Millimeter sign to use in the respective slider in multi-material painting gizmo,"
|
||||
"placed after the number with space in between.");
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
if (m_imgui->slider_float("##height_range_z_range", &m_height_range_z_range, HeightRangeZRangeMin, HeightRangeZRangeMax, format_str_angle.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();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
}
|
||||
@ -509,7 +570,7 @@ void GLGizmoMmuSegmentation::update_model_object() const
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
++idx;
|
||||
updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx].get());
|
||||
updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx]);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
@ -699,7 +760,7 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
const GLint position_id = shader->get_attrib_location("v_position");
|
||||
if (position_id != -1) {
|
||||
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)0));
|
||||
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)nullptr));
|
||||
glsafe(::glEnableVertexAttribArray(position_id));
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ public:
|
||||
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
|
||||
static const constexpr size_t EXTRUDERS_LIMIT = 16;
|
||||
|
||||
const float get_cursor_radius_min() const override { return CursorRadiusMin; }
|
||||
float get_cursor_radius_min() const override { return CursorRadiusMin; }
|
||||
|
||||
protected:
|
||||
ColorRGBA get_cursor_sphere_left_button_color() const override;
|
||||
|
@ -18,7 +18,9 @@
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <libslic3r/TriangleMeshSlicer.hpp>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
@ -139,10 +141,12 @@ void GLGizmoPainterBase::render_cursor()
|
||||
return;
|
||||
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
if (m_cursor_type == TriangleSelector::SPHERE)
|
||||
if (m_cursor_type == TriangleSelector::CursorType::SPHERE)
|
||||
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
|
||||
else if (m_cursor_type == TriangleSelector::CIRCLE)
|
||||
else if (m_cursor_type == TriangleSelector::CursorType::CIRCLE)
|
||||
render_cursor_circle();
|
||||
} else if (m_tool_type == ToolType::HEIGHT_RANGE) {
|
||||
render_cursor_height_range(trafo_matrices[m_rr.mesh_id]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +274,6 @@ void GLGizmoPainterBase::render_cursor_circle()
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
}
|
||||
|
||||
|
||||
void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
|
||||
{
|
||||
if (s_sphere == nullptr) {
|
||||
@ -314,6 +317,74 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
void GLGizmoPainterBase::render_cursor_height_range(const Transform3d &trafo) const {
|
||||
const ModelObject &model_object = *m_c->selection_info()->model_object();
|
||||
const BoundingBoxf3 mesh_bbox = model_object.volumes[m_rr.mesh_id]->mesh().bounding_box();
|
||||
|
||||
const std::array<float, 2> z_range = {
|
||||
std::min(m_rr.hit.z() - m_height_range_z_range / 2.f, float(mesh_bbox.max.z())),
|
||||
std::max(m_rr.hit.z() + m_height_range_z_range / 2.f, float(mesh_bbox.min.z()))
|
||||
};
|
||||
|
||||
std::vector<Polygons> slice_polygons_per_z;
|
||||
for (const float z: z_range)
|
||||
slice_polygons_per_z.emplace_back(slice_mesh(model_object.volumes[m_rr.mesh_id]->mesh().its, z, MeshSlicingParams()));
|
||||
|
||||
const size_t max_vertices_cnt = std::accumulate(slice_polygons_per_z.begin(), slice_polygons_per_z.end(), 0,
|
||||
[](const size_t sum, const Polygons &polygons) {
|
||||
return sum + count_points(polygons);
|
||||
});
|
||||
|
||||
GLModel::Geometry z_range_geometry;
|
||||
z_range_geometry.format = {GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3};
|
||||
z_range_geometry.reserve_vertices(max_vertices_cnt);
|
||||
z_range_geometry.reserve_indices(max_vertices_cnt);
|
||||
z_range_geometry.color = ColorRGBA::WHITE();
|
||||
|
||||
size_t vertices_cnt = 0;
|
||||
for (const float z: z_range) {
|
||||
const Polygons slice_polygons = slice_mesh(model_object.volumes[m_rr.mesh_id]->mesh().its, z, MeshSlicingParams());
|
||||
for (const Polygon &polygon: slice_polygons) {
|
||||
for (const Point &pt: polygon.points)
|
||||
z_range_geometry.add_vertex(Vec3f(unscaled<float>(pt.x()), unscaled<float>(pt.y()), z));
|
||||
|
||||
for (size_t pt_idx = 1; pt_idx < polygon.points.size(); ++pt_idx)
|
||||
z_range_geometry.add_line(vertices_cnt + pt_idx - 1, vertices_cnt + pt_idx);
|
||||
|
||||
z_range_geometry.add_line(vertices_cnt + polygon.points.size() - 1, vertices_cnt);
|
||||
|
||||
vertices_cnt += polygon.points.size();
|
||||
}
|
||||
}
|
||||
|
||||
GLModel z_range_model;
|
||||
if (!z_range_geometry.is_empty())
|
||||
z_range_model.init_from(std::move(z_range_geometry));
|
||||
|
||||
const Camera &camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d view_model_matrix = camera.get_view_matrix() * trafo;
|
||||
|
||||
GLShaderProgram *shader = wxGetApp().get_shader("mm_contour");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
shader->start_using();
|
||||
shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
|
||||
shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
|
||||
|
||||
const bool is_left_handed = Geometry::Transformation(view_model_matrix).is_left_handed();
|
||||
if (is_left_handed)
|
||||
glsafe(::glFrontFace(GL_CW));
|
||||
|
||||
z_range_model.render();
|
||||
|
||||
if (is_left_handed)
|
||||
glsafe(::glFrontFace(GL_CCW));
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const
|
||||
{
|
||||
@ -469,22 +540,38 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
: std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max());
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
} else if (m_tool_type == ToolType::SMART_FILL) {
|
||||
m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin)
|
||||
: std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
|
||||
} else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) {
|
||||
float &fill_angle = (m_tool_type == ToolType::SMART_FILL) ? m_smart_fill_angle : m_bucket_fill_angle;
|
||||
fill_angle = (action == SLAGizmoEventType::MouseWheelDown) ? std::max(fill_angle - SmartFillAngleStep, SmartFillAngleMin)
|
||||
: std::min(fill_angle + SmartFillAngleStep, SmartFillAngleMax);
|
||||
|
||||
m_parent.set_as_dirty();
|
||||
if (m_rr.mesh_id != -1) {
|
||||
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()];
|
||||
const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset();
|
||||
const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
|
||||
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()];
|
||||
const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
|
||||
const TriangleSelector::ClippingPlane &clipping_plane = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
|
||||
|
||||
if (m_tool_type == ToolType::SMART_FILL) {
|
||||
const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset();
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clipping_plane, m_smart_fill_angle, SmartFillGapArea,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES);
|
||||
} else {
|
||||
assert(m_tool_type == ToolType::BUCKET_FILL);
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clipping_plane, m_bucket_fill_angle, BucketFillGapArea,
|
||||
TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES);
|
||||
}
|
||||
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_seed_fill_last_mesh_id = m_rr.mesh_id;
|
||||
}
|
||||
return true;
|
||||
} else if (m_tool_type == ToolType::HEIGHT_RANGE) {
|
||||
m_height_range_z_range = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_height_range_z_range - HeightRangeZRangeStep, HeightRangeZRangeMin)
|
||||
: std::min(m_height_range_z_range + HeightRangeZRangeStep, HeightRangeZRangeMax);
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -556,18 +643,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
assert(mesh_idx < int(m_triangle_selectors.size()));
|
||||
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
|
||||
if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
|
||||
for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) {
|
||||
for (const ProjectedMousePosition &projected_mouse_position: projected_mouse_positions) {
|
||||
assert(projected_mouse_position.mesh_idx == mesh_idx);
|
||||
const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
|
||||
const int facet_idx = int(projected_mouse_position.facet_idx);
|
||||
const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
|
||||
const int facet_idx = int(projected_mouse_position.facet_idx);
|
||||
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
|
||||
if (m_tool_type == ToolType::SMART_FILL)
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true);
|
||||
|
||||
if (m_tool_type == ToolType::SMART_FILL) {
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, SmartFillGapArea,
|
||||
(m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES);
|
||||
} else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) {
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES);
|
||||
} else if (m_tool_type == ToolType::BUCKET_FILL) {
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES);
|
||||
}
|
||||
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
}
|
||||
@ -588,6 +678,16 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
}
|
||||
}
|
||||
} else if (m_tool_type == ToolType::HEIGHT_RANGE) {
|
||||
for (const ProjectedMousePosition &projected_mouse_position: projected_mouse_positions) {
|
||||
const Vec3f &mesh_hit = projected_mouse_position.mesh_hit;
|
||||
const int facet_idx = int(projected_mouse_position.facet_idx);
|
||||
const BoundingBoxf3 mesh_bbox = mo->volumes[projected_mouse_position.mesh_idx]->mesh().bounding_box();
|
||||
|
||||
std::unique_ptr<TriangleSelector::Cursor> cursor = std::make_unique<TriangleSelector::HeightRange>(mesh_hit, mesh_bbox, m_height_range_z_range, trafo_matrix, clp);
|
||||
m_triangle_selectors[mesh_idx]->select_patch(facet_idx, std::move(cursor), new_state, trafo_matrix_not_translate,
|
||||
m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
m_triangle_selectors[mesh_idx]->request_update_render_data();
|
||||
@ -646,12 +746,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
|
||||
if (m_tool_type == ToolType::SMART_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, SmartFillGapArea,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false);
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::NO);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true);
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES);
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_seed_fill_last_mesh_id = m_rr.mesh_id;
|
||||
return true;
|
||||
|
@ -97,9 +97,9 @@ public:
|
||||
// after all volumes (including transparent ones) are rendered.
|
||||
virtual void render_painter_gizmo() = 0;
|
||||
|
||||
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
|
||||
virtual const float get_cursor_radius_max() const { return CursorRadiusMax; }
|
||||
virtual const float get_cursor_radius_step() const { return CursorRadiusStep; }
|
||||
virtual float get_cursor_radius_min() const { return CursorRadiusMin; }
|
||||
virtual float get_cursor_radius_max() const { return CursorRadiusMax; }
|
||||
virtual float get_cursor_radius_step() const { return CursorRadiusStep; }
|
||||
|
||||
/// <summary>
|
||||
/// Implement when want to process mouse events in gizmo
|
||||
@ -112,9 +112,12 @@ public:
|
||||
|
||||
protected:
|
||||
virtual void render_triangles(const Selection& selection) const;
|
||||
|
||||
void render_cursor();
|
||||
void render_cursor_circle();
|
||||
void render_cursor_sphere(const Transform3d& trafo) const;
|
||||
void render_cursor_sphere(const Transform3d &trafo) const;
|
||||
void render_cursor_height_range(const Transform3d &trafo) const;
|
||||
|
||||
virtual void update_model_object() const = 0;
|
||||
virtual void update_from_model_object() = 0;
|
||||
|
||||
@ -132,12 +135,13 @@ protected:
|
||||
// For each model-part volume, store status and division of the triangles.
|
||||
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
|
||||
|
||||
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
|
||||
TriangleSelector::CursorType m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
enum class ToolType {
|
||||
BRUSH,
|
||||
BUCKET_FILL,
|
||||
SMART_FILL
|
||||
SMART_FILL,
|
||||
HEIGHT_RANGE
|
||||
};
|
||||
|
||||
struct ProjectedMousePosition
|
||||
@ -150,6 +154,8 @@ protected:
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_smart_fill_angle = 30.f;
|
||||
float m_bucket_fill_angle = 90.f;
|
||||
float m_height_range_z_range = 1.00f;
|
||||
|
||||
bool m_paint_on_overhangs_only = false;
|
||||
float m_highlight_by_angle_threshold_deg = 0.f;
|
||||
@ -162,6 +168,13 @@ protected:
|
||||
static constexpr float SmartFillAngleMax = 90.f;
|
||||
static constexpr float SmartFillAngleStep = 1.f;
|
||||
|
||||
static constexpr float HeightRangeZRangeMin = 0.1f;
|
||||
static constexpr float HeightRangeZRangeMax = 10.f;
|
||||
static constexpr float HeightRangeZRangeStep = 0.1f;
|
||||
|
||||
static constexpr float SmartFillGapArea = 0.02f;
|
||||
static constexpr float BucketFillGapArea = 0.02f;
|
||||
|
||||
// It stores the value of the previous mesh_id to which the seed fill was applied.
|
||||
// It is used to detect when the mouse has moved from one volume to another one.
|
||||
int m_seed_fill_last_mesh_id = -1;
|
||||
@ -192,7 +205,6 @@ private:
|
||||
|
||||
static std::shared_ptr<GLModel> s_sphere;
|
||||
|
||||
bool m_internal_stack_active = false;
|
||||
bool m_schedule_update = false;
|
||||
Vec2d m_last_mouse_click = Vec2d::Zero();
|
||||
|
||||
|
@ -79,7 +79,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||
if (! m_c->selection_info()->model_object())
|
||||
return;
|
||||
|
||||
const float approx_height = m_imgui->scaled(12.5f);
|
||||
const float approx_height = m_imgui->scaled(13.45f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
|
||||
@ -201,7 +201,7 @@ void GLGizmoSeam::update_model_object() const
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
++idx;
|
||||
updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get());
|
||||
updated |= mv->seam_facets.set(*m_triangle_selectors[idx]);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user