mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-13 23:35:59 +08:00
SPE-2003: Implement the height range tool into the multi-material painting gizmo.
The height range tool is inspired by BambuStudio.
This commit is contained in:
parent
535cbb2567
commit
e672072071
@ -1984,9 +1984,9 @@ TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const
|
||||
: 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
|
||||
@ -2220,4 +2220,54 @@ bool TriangleSelector::Capsule2D::is_any_edge_inside_cursor(const Triangle &tr,
|
||||
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
|
||||
|
@ -70,7 +70,8 @@ public:
|
||||
enum class CursorType {
|
||||
CIRCLE,
|
||||
SPHERE,
|
||||
POINTER
|
||||
POINTER,
|
||||
HEIGHT_RANGE
|
||||
};
|
||||
|
||||
enum class ForceReselection {
|
||||
@ -242,6 +243,26 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
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.
|
||||
|
@ -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_
|
||||
|
@ -119,11 +119,14 @@ 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["smart_fill_gap_area"] = _u8L("Smart fill gap");
|
||||
m_desc["split_triangles"] = _u8L("Split triangles");
|
||||
|
||||
m_desc["height_range_z_range"] = _u8L("Height range");
|
||||
|
||||
init_extruders_data();
|
||||
|
||||
return true;
|
||||
@ -264,17 +267,18 @@ 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(23.7f);
|
||||
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 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);
|
||||
@ -287,9 +291,14 @@ 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);
|
||||
|
||||
@ -302,15 +311,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
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, 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);
|
||||
@ -361,8 +370,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;
|
||||
@ -375,7 +384,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;
|
||||
@ -388,7 +397,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;
|
||||
@ -401,9 +410,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();
|
||||
|
||||
@ -450,7 +475,7 @@ 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) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":");
|
||||
std::string format_str_angle = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
|
||||
@ -475,6 +500,21 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,9 @@
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <libslic3r/TriangleMeshSlicer.hpp>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
@ -143,6 +145,8 @@ void GLGizmoPainterBase::render_cursor()
|
||||
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
|
||||
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
|
||||
{
|
||||
@ -471,7 +542,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
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);
|
||||
: std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
|
||||
m_parent.set_as_dirty();
|
||||
if (m_rr.mesh_id != -1) {
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
@ -485,6 +556,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
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,7 +632,7 @@ 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);
|
||||
@ -589,6 +665,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();
|
||||
|
@ -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;
|
||||
|
||||
@ -137,7 +140,8 @@ protected:
|
||||
enum class ToolType {
|
||||
BRUSH,
|
||||
BUCKET_FILL,
|
||||
SMART_FILL
|
||||
SMART_FILL,
|
||||
HEIGHT_RANGE
|
||||
};
|
||||
|
||||
struct ProjectedMousePosition
|
||||
@ -151,6 +155,7 @@ protected:
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_smart_fill_angle = 30.f;
|
||||
float m_smart_fill_gap_area = 0.02f;
|
||||
float m_height_range_z_range = 1.00f;
|
||||
|
||||
bool m_paint_on_overhangs_only = false;
|
||||
float m_highlight_by_angle_threshold_deg = 0.f;
|
||||
@ -166,6 +171,10 @@ protected:
|
||||
static constexpr float SmartFillGapAreaMin = 0.0f;
|
||||
static constexpr float SmartFillGapAreaMax = 1.f;
|
||||
|
||||
static constexpr float HeightRangeZRangeMin = 0.1f;
|
||||
static constexpr float HeightRangeZRangeMax = 10.f;
|
||||
static constexpr float HeightRangeZRangeStep = 0.1f;
|
||||
|
||||
// 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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user