mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-26 18:21:18 -06:00
Merge branch 'master' into fs_QuadricEdgeCollapse
This commit is contained in:
commit
86a3fd00a5
98 changed files with 21002 additions and 7709 deletions
|
|
@ -27,11 +27,6 @@ const std::array<float, 4> GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 };
|
|||
|
||||
GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
, m_cut_z(0.0)
|
||||
, m_max_z(0.0)
|
||||
, m_keep_upper(true)
|
||||
, m_keep_lower(true)
|
||||
, m_rotate_lower(false)
|
||||
{}
|
||||
|
||||
std::string GLGizmoCut::get_tooltip() const
|
||||
|
|
@ -58,9 +53,8 @@ std::string GLGizmoCut::on_get_name() const
|
|||
void GLGizmoCut::on_set_state()
|
||||
{
|
||||
// Reset m_cut_z on gizmo activation
|
||||
if (get_state() == On) {
|
||||
m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0;
|
||||
}
|
||||
if (get_state() == On)
|
||||
m_cut_z = bounding_box().center().z();
|
||||
}
|
||||
|
||||
bool GLGizmoCut::on_is_activable() const
|
||||
|
|
@ -74,13 +68,12 @@ void GLGizmoCut::on_start_dragging()
|
|||
if (m_hover_id == -1)
|
||||
return;
|
||||
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const BoundingBoxf3& box = selection.get_bounding_box();
|
||||
const BoundingBoxf3 box = bounding_box();
|
||||
m_max_z = box.max.z();
|
||||
m_start_z = m_cut_z;
|
||||
update_max_z(selection);
|
||||
m_drag_pos = m_grabbers[m_hover_id].center;
|
||||
m_drag_center = box.center();
|
||||
m_drag_center(2) = m_cut_z;
|
||||
m_drag_center.z() = m_cut_z;
|
||||
}
|
||||
|
||||
void GLGizmoCut::on_update(const UpdateData& data)
|
||||
|
|
@ -91,13 +84,11 @@ void GLGizmoCut::on_update(const UpdateData& data)
|
|||
|
||||
void GLGizmoCut::on_render() const
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
update_max_z(selection);
|
||||
|
||||
const BoundingBoxf3& box = selection.get_bounding_box();
|
||||
BoundingBoxf3 box = bounding_box();
|
||||
Vec3d plane_center = box.center();
|
||||
plane_center.z() = m_cut_z;
|
||||
m_max_z = box.max.z();
|
||||
set_cut_z(m_cut_z);
|
||||
|
||||
const float min_x = box.min.x() - Margin;
|
||||
const float max_x = box.max.x() + Margin;
|
||||
|
|
@ -160,10 +151,10 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
|||
|
||||
m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
|
||||
// adjust window position to avoid overlap the view toolbar
|
||||
float win_h = ImGui::GetWindowHeight();
|
||||
const float win_h = ImGui::GetWindowHeight();
|
||||
y = std::min(y, bottom_limit - win_h);
|
||||
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
|
||||
if (last_h != win_h || last_y != y) {
|
||||
|
|
@ -198,7 +189,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
|||
|
||||
ImGui::Separator();
|
||||
|
||||
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z < m_cut_z);
|
||||
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z);
|
||||
const bool cut_clicked = m_imgui->button(_L("Perform cut"));
|
||||
m_imgui->disabled_end();
|
||||
|
||||
|
|
@ -208,12 +199,6 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
|||
perform_cut(m_parent.get_selection());
|
||||
}
|
||||
|
||||
void GLGizmoCut::update_max_z(const Selection& selection) const
|
||||
{
|
||||
m_max_z = selection.get_bounding_box().size()(2);
|
||||
set_cut_z(m_cut_z);
|
||||
}
|
||||
|
||||
void GLGizmoCut::set_cut_z(double cut_z) const
|
||||
{
|
||||
// Clamp the plane to the object's bounding box
|
||||
|
|
@ -229,10 +214,10 @@ void GLGizmoCut::perform_cut(const Selection& selection)
|
|||
|
||||
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
|
||||
const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
coordf_t object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
|
||||
const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
|
||||
|
||||
if (object_cut_z > 0.)
|
||||
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
|
||||
if (0.0 < object_cut_z && object_cut_z < m_max_z)
|
||||
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
|
||||
only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
|
||||
only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) |
|
||||
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower));
|
||||
|
|
@ -247,16 +232,15 @@ double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
|
|||
|
||||
const Vec3d starting_vec = m_drag_pos - m_drag_center;
|
||||
const double len_starting_vec = starting_vec.norm();
|
||||
if (len_starting_vec != 0.0)
|
||||
{
|
||||
Vec3d mouse_dir = mouse_ray.unit_vector();
|
||||
if (len_starting_vec != 0.0) {
|
||||
const Vec3d mouse_dir = mouse_ray.unit_vector();
|
||||
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
|
||||
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
|
||||
// in our case plane normal and ray direction are the same (orthogonal view)
|
||||
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
|
||||
Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
|
||||
const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
|
||||
// vector from the starting position to the found intersection
|
||||
Vec3d inters_vec = inters - m_drag_pos;
|
||||
const Vec3d inters_vec = inters - m_drag_pos;
|
||||
|
||||
// finds projection of the vector along the staring direction
|
||||
projection = inters_vec.dot(starting_vec.normalized());
|
||||
|
|
@ -264,6 +248,18 @@ double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
|
|||
return projection;
|
||||
}
|
||||
|
||||
BoundingBoxf3 GLGizmoCut::bounding_box() const
|
||||
{
|
||||
BoundingBoxf3 ret;
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const Selection::IndicesList& idxs = selection.get_volume_idxs();
|
||||
for (unsigned int i : idxs) {
|
||||
const GLVolume* volume = selection.get_volume(i);
|
||||
if (!volume->is_modifier)
|
||||
ret.merge(volume->transformed_convex_hull_bounding_box());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@ class GLGizmoCut : public GLGizmoBase
|
|||
static const double Margin;
|
||||
static const std::array<float, 4> GrabberColor;
|
||||
|
||||
mutable double m_cut_z;
|
||||
double m_start_z;
|
||||
mutable double m_max_z;
|
||||
mutable double m_cut_z{ 0.0 };
|
||||
mutable double m_max_z{ 0.0 };
|
||||
double m_start_z{ 0.0 };
|
||||
Vec3d m_drag_pos;
|
||||
Vec3d m_drag_center;
|
||||
bool m_keep_upper;
|
||||
bool m_keep_lower;
|
||||
bool m_rotate_lower;
|
||||
bool m_keep_upper{ true };
|
||||
bool m_keep_lower{ true };
|
||||
bool m_rotate_lower{ false };
|
||||
|
||||
public:
|
||||
GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
|
@ -32,7 +32,7 @@ public:
|
|||
|
||||
protected:
|
||||
virtual bool on_init() override;
|
||||
virtual void on_load(cereal::BinaryInputArchive& ar) override{ ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
|
||||
virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
|
||||
virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
|
||||
virtual std::string on_get_name() const override;
|
||||
virtual void on_set_state() override;
|
||||
|
|
@ -44,9 +44,9 @@ protected:
|
|||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
private:
|
||||
void update_max_z(const Selection& selection) const;
|
||||
void perform_cut(const Selection& selection);
|
||||
double calc_projection(const Linef3& mouse_ray) const;
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
|||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +193,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
|||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
|
||||
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
|
||||
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
|
|
@ -285,13 +288,12 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
|
|||
|
||||
// Now calculate dot product of vert_direction and facets' normals.
|
||||
int idx = -1;
|
||||
for (const stl_facet& facet : mv->mesh().stl.facet_start) {
|
||||
for (const stl_facet &facet : mv->mesh().stl.facet_start) {
|
||||
++idx;
|
||||
if (facet.normal.dot(down) > dot_limit)
|
||||
m_triangle_selectors[mesh_id]->set_facet(idx,
|
||||
block
|
||||
? EnforcerBlockerType::BLOCKER
|
||||
: EnforcerBlockerType::ENFORCER);
|
||||
if (facet.normal.dot(down) > dot_limit) {
|
||||
m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER);
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -346,6 +348,7 @@ void GLGizmoFdmSupports::update_from_model_object()
|
|||
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
|
||||
m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data());
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,16 +107,24 @@ bool GLGizmoMmuSegmentation::on_init()
|
|||
m_desc["reset_direction"] = _L("Reset direction");
|
||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||
m_desc["cursor_size"] = _L("Brush size") + ": ";
|
||||
m_desc["cursor_type"] = _L("Brush shape") + ": ";
|
||||
m_desc["cursor_type"] = _L("Brush shape");
|
||||
m_desc["first_color_caption"] = _L("Left mouse button") + ": ";
|
||||
m_desc["first_color"] = _L("First color");
|
||||
m_desc["second_color_caption"] = _L("Right mouse button") + ": ";
|
||||
m_desc["second_color"] = _L("Second color");
|
||||
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
|
||||
m_desc["remove"] = _L("Remove painted color");
|
||||
m_desc["remove_all"] = _L("Remove all painted colors");
|
||||
m_desc["remove_all"] = _L("Remove all painted areas");
|
||||
m_desc["circle"] = _L("Circle");
|
||||
m_desc["sphere"] = _L("Sphere");
|
||||
m_desc["pointer"] = _L("Pointer");
|
||||
|
||||
m_desc["tool_type"] = _L("Tool type");
|
||||
m_desc["tool_brush"] = _L("Brush");
|
||||
m_desc["tool_seed_fill"] = _L("Seed fill");
|
||||
m_desc["tool_bucket_fill"] = _L("Bucket fill");
|
||||
|
||||
m_desc["seed_fill"] = _L("Seed fill");
|
||||
m_desc["seed_fill_angle"] = _L("Seed fill angle");
|
||||
|
||||
init_extruders_data();
|
||||
|
|
@ -147,8 +155,8 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection)
|
|||
return;
|
||||
|
||||
ModelObject *model_object = m_c->selection_info()->model_object();
|
||||
int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
if (prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
|
||||
if (int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
|
||||
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
|
||||
show_notification_extruders_limit_exceeded();
|
||||
|
||||
|
|
@ -222,7 +230,7 @@ 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.0f);
|
||||
const float approx_height = m_imgui->scaled(25.0f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
|
|
@ -232,10 +240,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
|
||||
m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
|
||||
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||
const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
|
||||
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
|
||||
const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
const float seed_fill_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
|
||||
|
||||
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
||||
const float buttons_width = m_imgui->scaled(0.5f);
|
||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||
|
|
@ -243,6 +253,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x,
|
||||
m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f);
|
||||
|
||||
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_seed_fill = m_imgui->calc_text_size(m_desc["tool_seed_fill"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
float caption_max = 0.f;
|
||||
float total_text_max = 0.;
|
||||
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) {
|
||||
|
|
@ -252,10 +266,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
caption_max += m_imgui->scaled(1.f);
|
||||
total_text_max += m_imgui->scaled(1.f);
|
||||
|
||||
float window_width = minimal_slider_width + std::max(autoset_slider_left, std::max(cursor_slider_left, clipping_slider_left));
|
||||
float sliders_width = std::max(seed_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
|
||||
float window_width = minimal_slider_width + sliders_width;
|
||||
window_width = std::max(window_width, total_text_max);
|
||||
window_width = std::max(window_width, button_width);
|
||||
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2);
|
||||
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_seed_fill);
|
||||
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
|
||||
|
||||
auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) {
|
||||
|
|
@ -292,91 +308,166 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
if(ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel))
|
||||
m_modified_extruders_colors[m_second_selected_extruder_idx] = {second_color.x, second_color.y, second_color.z, second_color.w};
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (m_imgui->checkbox(_L("Seed fill"), m_seed_fill_enabled))
|
||||
if (!m_seed_fill_enabled)
|
||||
for (auto &triangle_selector : m_triangle_selectors)
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
|
||||
m_imgui->text(m_desc["seed_fill_angle"] + ":");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo,"
|
||||
"placed after the number with no whitespace in between.");
|
||||
ImGui::SameLine(autoset_slider_left);
|
||||
ImGui::PushItemWidth(window_width - autoset_slider_left);
|
||||
m_imgui->disabled_begin(!m_seed_fill_enabled);
|
||||
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, 0.f, 90.f, format_str.data());
|
||||
m_imgui->disabled_end();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
|
||||
ModelObject *mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume *mv : mo->volumes) {
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
}
|
||||
}
|
||||
|
||||
update_model_object();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_size"));
|
||||
ImGui::SameLine(cursor_slider_left);
|
||||
ImGui::PushItemWidth(window_width - cursor_slider_left);
|
||||
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_type"));
|
||||
ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_width1);
|
||||
m_imgui->text(m_desc.at("tool_type"));
|
||||
|
||||
bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE;
|
||||
if (m_imgui->radio_button(m_desc["sphere"], sphere_sel))
|
||||
sphere_sel = true;
|
||||
float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_seed_fill + m_imgui->scaled(2.f)) / 2.f;
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::SameLine(tool_type_offset + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_brush);
|
||||
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH))
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BRUSH;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
|
||||
ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_width2);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["circle"], !sphere_sel))
|
||||
sphere_sel = false;
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
m_cursor_type = sphere_sel ? TriangleSelector::CursorType::SPHERE : TriangleSelector::CursorType::CIRCLE;
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_bucket_fill + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_seed_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_seed_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SEED_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::SEED_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
|
||||
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(m_tool_type == ToolType::BRUSH) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_type"));
|
||||
ImGui::NewLine();
|
||||
|
||||
float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(2.f)) / 2.f;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::SameLine(cursor_type_offset + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_sphere);
|
||||
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(cursor_type_offset +cursor_type_radio_sphere + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_circle);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
|
||||
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_pointer);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
|
||||
m_cursor_type = TriangleSelector::CursorType::POINTER;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_size"));
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
m_imgui->disabled_end();
|
||||
|
||||
ImGui::Separator();
|
||||
} else if(m_tool_type == ToolType::SEED_FILL) {
|
||||
m_imgui->text(m_desc["seed_fill_angle"] + ":");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
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.");
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data());
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
if (m_c->object_clipper()->get_position() == 0.f) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("clipping_of_view"));
|
||||
|
|
@ -386,8 +477,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine(clipping_slider_left);
|
||||
ImGui::PushItemWidth(window_width - clipping_slider_left);
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
auto clp_dist = float(m_c->object_clipper()->get_position());
|
||||
if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
|
||||
m_c->object_clipper()->set_position(clp_dist, true);
|
||||
|
|
@ -398,6 +489,23 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
|
||||
ModelObject * mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume *mv : mo->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
|
||||
update_model_object();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
m_imgui->end();
|
||||
}
|
||||
|
||||
|
|
@ -437,8 +545,9 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
|
|||
const TriangleMesh *mesh = &mv->mesh();
|
||||
|
||||
int extruder_idx = (mv->extruder_id() > 0) ? mv->extruder_id() - 1 : 0;
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmuGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[size_t(extruder_idx)]));
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[size_t(extruder_idx)]));
|
||||
m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data());
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo);
|
||||
}
|
||||
|
|
@ -446,6 +555,13 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
|
|||
void GLGizmoMmuSegmentation::update_from_model_object()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
||||
// Extruder colors need to be reloaded before calling init_model_triangle_selectors to render painted triangles
|
||||
// using colors from loaded 3MF and not from printer profile in Slicer.
|
||||
if (int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors)
|
||||
this->init_extruders_data();
|
||||
|
||||
this->init_model_triangle_selectors();
|
||||
}
|
||||
|
||||
|
|
@ -466,56 +582,60 @@ std::array<float, 4> GLGizmoMmuSegmentation::get_cursor_sphere_right_button_colo
|
|||
return {color[0], color[1], color[2], 0.25f};
|
||||
}
|
||||
|
||||
void TriangleSelectorMmuGui::render(ImGuiWrapper *imgui)
|
||||
void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
|
||||
{
|
||||
static constexpr std::array<float, 4> seed_fill_color{0.f, 1.f, 0.44f, 1.f};
|
||||
|
||||
std::vector<int> color_cnt(m_iva_colors.size());
|
||||
int seed_fill_cnt = 0;
|
||||
for (auto &iva_color : m_iva_colors)
|
||||
iva_color.release_geometry();
|
||||
m_iva_seed_fill.release_geometry();
|
||||
|
||||
auto append_triangle = [this](GLIndexedVertexArray &iva, int &cnt, const Triangle &tr) -> void {
|
||||
for (int i = 0; i < 3; ++i)
|
||||
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
|
||||
iva.push_triangle(cnt, cnt + 1, cnt + 2);
|
||||
cnt += 3;
|
||||
};
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx) {
|
||||
for (const Triangle &tr : m_triangles) {
|
||||
if (!tr.valid() || tr.is_split() || tr.is_selected_by_seed_fill() || tr.get_state() != EnforcerBlockerType(color_idx))
|
||||
continue;
|
||||
append_triangle(m_iva_colors[color_idx], color_cnt[color_idx], tr);
|
||||
}
|
||||
}
|
||||
|
||||
for (const Triangle &tr : m_triangles) {
|
||||
if (!tr.valid() || tr.is_split() || !tr.is_selected_by_seed_fill())
|
||||
continue;
|
||||
append_triangle(m_iva_seed_fill, seed_fill_cnt, tr);
|
||||
}
|
||||
|
||||
for (auto &iva_color : m_iva_colors)
|
||||
iva_color.finalize_geometry(true);
|
||||
m_iva_seed_fill.finalize_geometry(true);
|
||||
if (m_update_render_data)
|
||||
update_render_data();
|
||||
|
||||
auto *shader = wxGetApp().get_current_shader();
|
||||
if (!shader)
|
||||
return;
|
||||
assert(shader->get_name() == "gouraud");
|
||||
ScopeGuard guard([shader]() { if (shader) shader->set_uniform("compute_triangle_normals_in_fs", false);});
|
||||
shader->set_uniform("compute_triangle_normals_in_fs", true);
|
||||
|
||||
auto render = [&shader](const GLIndexedVertexArray &iva, const std::array<float, 4> &color) -> void {
|
||||
if (iva.has_VBOs()) {
|
||||
shader->set_uniform("uniform_color", color);
|
||||
iva.render();
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
|
||||
if (m_gizmo_scene.has_VBOs(color_idx)) {
|
||||
shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color :
|
||||
color_idx == (m_gizmo_scene.triangle_indices.size() - 1) ? seed_fill_color :
|
||||
m_colors[color_idx - 1]);
|
||||
m_gizmo_scene.render(color_idx);
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx)
|
||||
render(m_iva_colors[color_idx], (color_idx == 0) ? m_default_volume_color : m_colors[color_idx - 1]);
|
||||
render(m_iva_seed_fill, seed_fill_color);
|
||||
m_update_render_data = false;
|
||||
}
|
||||
|
||||
void TriangleSelectorMmGui::update_render_data()
|
||||
{
|
||||
m_gizmo_scene.release_geometry();
|
||||
m_vertices.reserve(m_vertices.size() * 3);
|
||||
for (const Vertex &vr : m_vertices) {
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.x());
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.y());
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.z());
|
||||
}
|
||||
m_gizmo_scene.finalize_vertices();
|
||||
|
||||
for (const Triangle &tr : m_triangles)
|
||||
if (tr.valid() && !tr.is_split()) {
|
||||
int color = int(tr.get_state());
|
||||
std::vector<int> &iva = tr.is_selected_by_seed_fill() ? m_gizmo_scene.triangle_indices.back() :
|
||||
color < int(m_gizmo_scene.triangle_indices.size() - 1) ? m_gizmo_scene.triangle_indices[color] :
|
||||
m_gizmo_scene.triangle_indices.front();
|
||||
if (iva.size() + 3 > iva.capacity())
|
||||
iva.reserve(next_highest_power_of_2(iva.size() + 3));
|
||||
|
||||
iva.emplace_back(tr.verts_idxs[0]);
|
||||
iva.emplace_back(tr.verts_idxs[1]);
|
||||
iva.emplace_back(tr.verts_idxs[2]);
|
||||
}
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
|
||||
m_gizmo_scene.triangle_indices_sizes[color_idx] = m_gizmo_scene.triangle_indices[color_idx].size();
|
||||
|
||||
m_gizmo_scene.finalize_triangle_indices();
|
||||
}
|
||||
|
||||
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
|
||||
|
|
@ -530,4 +650,76 @@ wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GL
|
|||
return action_name;
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::release_geometry() {
|
||||
if (this->vertices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->vertices_VBO_id));
|
||||
this->vertices_VBO_id = 0;
|
||||
}
|
||||
for(auto &triangle_indices_VBO_id : triangle_indices_VBO_ids) {
|
||||
glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id));
|
||||
triangle_indices_VBO_id = 0;
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
|
||||
{
|
||||
assert(triangle_indices_idx < this->triangle_indices_VBO_ids.size());
|
||||
assert(this->triangle_indices_sizes.size() == this->triangle_indices_VBO_ids.size());
|
||||
assert(this->vertices_VBO_id != 0);
|
||||
assert(this->triangle_indices_VBO_ids[triangle_indices_idx] != 0);
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), (const void*)(0 * sizeof(float))));
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
// Render using the Vertex Buffer Objects.
|
||||
if (this->triangle_indices_sizes[triangle_indices_idx] > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[triangle_indices_idx]));
|
||||
glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_sizes[triangle_indices_idx]), GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_vertices()
|
||||
{
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
if (!this->vertices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->vertices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * 4, this->vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->vertices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_triangle_indices()
|
||||
{
|
||||
assert(std::all_of(triangle_indices_VBO_ids.cbegin(), triangle_indices_VBO_ids.cend(), [](const auto &ti_VBO_id) { return ti_VBO_id == 0; }));
|
||||
|
||||
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
|
||||
for (size_t buffer_idx = 0; buffer_idx < this->triangle_indices.size(); ++buffer_idx)
|
||||
if (!this->triangle_indices[buffer_idx].empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices[buffer_idx].size() * 4, this->triangle_indices[buffer_idx].data(),
|
||||
GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->triangle_indices[buffer_idx].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_geometry()
|
||||
{
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
|
||||
finalize_vertices();
|
||||
finalize_triangle_indices();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -5,24 +5,80 @@
|
|||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
class TriangleSelectorMmuGui : public TriangleSelectorGUI {
|
||||
class GLMmSegmentationGizmo3DScene
|
||||
{
|
||||
public:
|
||||
explicit TriangleSelectorMmuGui(const TriangleMesh& mesh, const std::vector<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color)
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color) {
|
||||
// Plus 1 is because the first position is allocated for non-painted triangles.
|
||||
m_iva_colors = std::vector<GLIndexedVertexArray>(colors.size() + 1);
|
||||
GLMmSegmentationGizmo3DScene() = delete;
|
||||
|
||||
explicit GLMmSegmentationGizmo3DScene(size_t triangle_indices_buffers_count)
|
||||
{
|
||||
this->triangle_indices = std::vector<std::vector<int>>(triangle_indices_buffers_count);
|
||||
this->triangle_indices_sizes = std::vector<size_t>(triangle_indices_buffers_count);
|
||||
this->triangle_indices_VBO_ids = std::vector<unsigned int>(triangle_indices_buffers_count);
|
||||
}
|
||||
~TriangleSelectorMmuGui() override = default;
|
||||
|
||||
virtual ~GLMmSegmentationGizmo3DScene() { release_geometry(); }
|
||||
|
||||
[[nodiscard]] inline bool has_VBOs(size_t triangle_indices_idx) const
|
||||
{
|
||||
assert(triangle_indices_idx < this->triangle_indices.size());
|
||||
return this->triangle_indices_VBO_ids[triangle_indices_idx] != 0;
|
||||
}
|
||||
|
||||
// Finalize the initialization of the geometry and indices, upload the geometry and indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_geometry();
|
||||
// Release the geometry data, release OpenGL VBOs.
|
||||
void release_geometry();
|
||||
// Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_vertices();
|
||||
// Finalize the initialization of the indices, upload the indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_triangle_indices();
|
||||
|
||||
void clear()
|
||||
{
|
||||
this->vertices.clear();
|
||||
for (std::vector<int> &ti : this->triangle_indices)
|
||||
ti.clear();
|
||||
|
||||
for (size_t &triangle_indices_size : this->triangle_indices_sizes)
|
||||
triangle_indices_size = 0;
|
||||
}
|
||||
|
||||
void render(size_t triangle_indices_idx) const;
|
||||
|
||||
std::vector<float> vertices;
|
||||
std::vector<std::vector<int>> triangle_indices;
|
||||
|
||||
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
|
||||
// the above mentioned std::vectors are cleared and the following variables keep their original length.
|
||||
std::vector<size_t> triangle_indices_sizes;
|
||||
|
||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||
// Zero if the VBOs are not sent to GPU yet.
|
||||
unsigned int vertices_VBO_id{0};
|
||||
std::vector<unsigned int> triangle_indices_VBO_ids;
|
||||
};
|
||||
|
||||
class TriangleSelectorMmGui : public TriangleSelectorGUI {
|
||||
public:
|
||||
// Plus 2 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the last position is allocated for seed fill.
|
||||
explicit TriangleSelectorMmGui(const TriangleMesh &mesh, const std::vector<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color)
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(colors.size() + 2) {}
|
||||
~TriangleSelectorMmGui() override = default;
|
||||
|
||||
// Render current selection. Transformation matrices are supposed
|
||||
// to be already set.
|
||||
void render(ImGuiWrapper* imgui) override;
|
||||
|
||||
private:
|
||||
void update_render_data();
|
||||
|
||||
const std::vector<std::array<float, 4>> &m_colors;
|
||||
std::vector<GLIndexedVertexArray> m_iva_colors;
|
||||
const std::array<float, 4> m_default_volume_color;
|
||||
GLIndexedVertexArray m_iva_seed_fill;
|
||||
GLMmSegmentationGizmo3DScene m_gizmo_scene;
|
||||
};
|
||||
|
||||
class GLGizmoMmuSegmentation : public GLGizmoPainterBase
|
||||
|
|
|
|||
|
|
@ -194,10 +194,10 @@ void GLGizmoPainterBase::render_cursor() const
|
|||
if (m_rr.mesh_id == -1)
|
||||
return;
|
||||
|
||||
if (!m_seed_fill_enabled) {
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
if (m_cursor_type == TriangleSelector::SPHERE)
|
||||
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
|
||||
else
|
||||
else if (m_cursor_type == TriangleSelector::CIRCLE)
|
||||
render_cursor_circle();
|
||||
}
|
||||
}
|
||||
|
|
@ -307,11 +307,19 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
return true;
|
||||
}
|
||||
else if (alt_down) {
|
||||
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown
|
||||
? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
|
||||
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
|
||||
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
} else if (m_tool_type == ToolType::SEED_FILL) {
|
||||
m_seed_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_seed_fill_angle - SeedFillAngleStep, SeedFillAngleMin)
|
||||
: std::min(m_seed_fill_angle + SeedFillAngleStep, SeedFillAngleMax);
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -396,19 +404,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
|
||||
|
||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
if (m_seed_fill_enabled) {
|
||||
if (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
} else
|
||||
} else if (m_tool_type == ToolType::BRUSH)
|
||||
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
|
||||
new_state, trafo_matrix, m_triangle_splitting_enabled);
|
||||
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_last_mouse_click = mouse_position;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::Moving && m_seed_fill_enabled) {
|
||||
if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) {
|
||||
if (m_triangle_selectors.empty())
|
||||
return false;
|
||||
|
||||
|
|
@ -428,8 +438,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
update_raycast_cache(mouse_position, camera, trafo_matrices);
|
||||
|
||||
auto seed_fill_unselect_all = [this]() {
|
||||
for (auto &triangle_selector : m_triangle_selectors)
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
};
|
||||
|
||||
if (m_rr.mesh_id == -1) {
|
||||
|
|
@ -446,7 +458,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
seed_fill_unselect_all();
|
||||
|
||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle);
|
||||
if (m_tool_type == ToolType::SEED_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle);
|
||||
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), false);
|
||||
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), true);
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_seed_fill_last_mesh_id = m_rr.mesh_id;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -589,28 +607,11 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
|
|||
static constexpr std::array<float, 4> enforcers_color{0.47f, 0.47f, 1.f, 1.f};
|
||||
static constexpr std::array<float, 4> blockers_color{1.f, 0.44f, 0.44f, 1.f};
|
||||
|
||||
int enf_cnt = 0;
|
||||
int blc_cnt = 0;
|
||||
|
||||
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
|
||||
iva->release_geometry();
|
||||
|
||||
for (const Triangle& tr : m_triangles) {
|
||||
if (!tr.valid() || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE)
|
||||
continue;
|
||||
|
||||
GLIndexedVertexArray &iva = tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : m_iva_blockers;
|
||||
int & cnt = tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : blc_cnt;
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
|
||||
iva.push_triangle(cnt, cnt + 1, cnt + 2);
|
||||
cnt += 3;
|
||||
if (m_update_render_data) {
|
||||
update_render_data();
|
||||
m_update_render_data = false;
|
||||
}
|
||||
|
||||
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
|
||||
iva->finalize_geometry(true);
|
||||
|
||||
auto* shader = wxGetApp().get_current_shader();
|
||||
if (! shader)
|
||||
return;
|
||||
|
|
@ -635,6 +636,33 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
|
|||
|
||||
|
||||
|
||||
void TriangleSelectorGUI::update_render_data()
|
||||
{
|
||||
int enf_cnt = 0;
|
||||
int blc_cnt = 0;
|
||||
|
||||
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
|
||||
iva->release_geometry();
|
||||
|
||||
for (const Triangle &tr : m_triangles) {
|
||||
if (!tr.valid() || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE)
|
||||
continue;
|
||||
|
||||
GLIndexedVertexArray &iva = tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : m_iva_blockers;
|
||||
int & cnt = tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : blc_cnt;
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
|
||||
iva.push_triangle(cnt, cnt + 1, cnt + 2);
|
||||
cnt += 3;
|
||||
}
|
||||
|
||||
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
|
||||
iva->finalize_geometry(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
|
||||
{
|
||||
|
|
@ -685,7 +713,7 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
|
|||
va = &m_varrays[ORIGINAL];
|
||||
cnt = &cnts[ORIGINAL];
|
||||
}
|
||||
else if (tr.valid) {
|
||||
else if (tr.valid()) {
|
||||
va = &m_varrays[SPLIT];
|
||||
cnt = &cnts[SPLIT];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,13 +38,20 @@ public:
|
|||
virtual void render(ImGuiWrapper *imgui);
|
||||
void render() { this->render(nullptr); }
|
||||
|
||||
void request_update_render_data() { m_update_render_data = true; };
|
||||
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
void render_debug(ImGuiWrapper* imgui);
|
||||
bool m_show_triangles{false};
|
||||
bool m_show_invalid{false};
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool m_update_render_data = false;
|
||||
|
||||
private:
|
||||
void update_render_data();
|
||||
|
||||
GLIndexedVertexArray m_iva_enforcers;
|
||||
GLIndexedVertexArray m_iva_blockers;
|
||||
std::array<GLIndexedVertexArray, 3> m_varrays;
|
||||
|
|
@ -99,13 +106,23 @@ protected:
|
|||
|
||||
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
|
||||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
bool m_seed_fill_enabled = false;
|
||||
float m_seed_fill_angle = 0.f;
|
||||
enum class ToolType {
|
||||
BRUSH,
|
||||
BUCKET_FILL,
|
||||
SEED_FILL
|
||||
};
|
||||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_seed_fill_angle = 0.f;
|
||||
|
||||
static constexpr float SeedFillAngleMin = 0.0f;
|
||||
static constexpr float SeedFillAngleMax = 90.f;
|
||||
static constexpr float SeedFillAngleStep = 1.f;
|
||||
|
||||
// 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;
|
||||
int m_seed_fill_last_mesh_id = -1;
|
||||
|
||||
enum class Button {
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
|||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,6 +148,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
|||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
|
||||
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
|
||||
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
|
|
@ -257,6 +260,7 @@ void GLGizmoSeam::update_from_model_object()
|
|||
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
|
||||
m_triangle_selectors.back()->deserialize(mv->seam_facets.get_data());
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue