diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index ea3d87888..45f04b1df 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -11,6 +11,8 @@ #define ENABLE_SELECTION_DEBUG_OUTPUT 0 // Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active #define ENABLE_RENDER_SELECTION_CENTER 0 +// Shows an imgui dialog with render related data +#define ENABLE_RENDER_STATISTICS 0 //==================== diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 555918765..570e23baa 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -47,6 +47,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoFlatten.hpp GUI/Gizmos/GLGizmoCut.cpp GUI/Gizmos/GLGizmoCut.hpp + GUI/GLSelectionRectangle.cpp + GUI/GLSelectionRectangle.hpp GUI/GLTexture.hpp GUI/GLTexture.cpp GUI/GLToolbar.hpp diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 9038e388c..037f70962 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -223,7 +223,8 @@ void GLIndexedVertexArray::render( } const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; -const float GLVolume::HOVER_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f }; +const float GLVolume::HOVER_SELECT_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f }; +const float GLVolume::HOVER_DESELECT_COLOR[4] = { 1.0f, 0.75f, 0.75f, 1.0f }; const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f }; const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f }; const float GLVolume::DISABLED_COLOR[4] = { 0.25f, 0.25f, 0.25f, 1.0f }; @@ -251,7 +252,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) , zoom_to_volumes(true) , shader_outside_printer_detection_enabled(false) , is_outside(false) - , hover(false) + , hover(HS_None) , is_modifier(false) , is_wipe_tower(false) , is_extrusion_path(false) @@ -291,10 +292,12 @@ void GLVolume::set_render_color() if (force_native_color) set_render_color(color, 4); else { - if (selected) + if (hover == HS_Select) + set_render_color(HOVER_SELECT_COLOR, 4); + else if (hover == HS_Deselect) + set_render_color(HOVER_DESELECT_COLOR, 4); + else if (selected) set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); - else if (hover) - set_render_color(HOVER_COLOR, 4); else if (disabled) set_render_color(DISABLED_COLOR, 4); else if (is_outside && shader_outside_printer_detection_enabled) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 88547359e..377d89fe7 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -225,7 +225,8 @@ private: class GLVolume { public: static const float SELECTED_COLOR[4]; - static const float HOVER_COLOR[4]; + static const float HOVER_SELECT_COLOR[4]; + static const float HOVER_DESELECT_COLOR[4]; static const float OUTSIDE_COLOR[4]; static const float SELECTED_OUTSIDE_COLOR[4]; static const float DISABLED_COLOR[4]; @@ -233,6 +234,13 @@ public: static const float SLA_SUPPORT_COLOR[4]; static const float SLA_PAD_COLOR[4]; + enum EHoverState : unsigned char + { + HS_None, + HS_Select, + HS_Deselect + }; + GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} ~GLVolume(); @@ -296,8 +304,8 @@ public: bool shader_outside_printer_detection_enabled; // Wheter or not this volume is outside print volume. bool is_outside; - // Boolean: Is mouse over this object? - bool hover; + // Is mouse or rectangle selection over this object to select/deselect it ? + EHoverState hover; // Wheter or not this volume has been generated from a modifier bool is_modifier; // Wheter or not this volume has been generated from the wipe tower diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3c7365d15..586ab910f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -52,6 +52,9 @@ #include #include #include +#if ENABLE_RENDER_STATISTICS +#include +#endif // ENABLE_RENDER_STATISTICS static const float TRACKBALLSIZE = 0.8f; static const float GROUND_Z = -0.02f; @@ -672,6 +675,7 @@ GLCanvas3D::Mouse::Mouse() : dragging(false) , position(DBL_MAX, DBL_MAX) , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) + , ignore_left_up(false) { } @@ -1228,7 +1232,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_initialized(false) , m_use_VBOs(false) , m_apply_zoom_to_volumes_filter(false) - , m_hover_volume_id(-1) , m_legend_texture_enabled(false) , m_picking_enabled(false) , m_moving_enabled(false) @@ -1237,6 +1240,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_regenerate_volumes(true) , m_moving(false) , m_tab_down(false) + , m_cursor_type(Standard) , m_color_by("volume") , m_reload_delayed(false) , m_render_sla_auxiliaries(true) @@ -1588,6 +1592,10 @@ void GLCanvas3D::render() if (!_set_current() || !_3DScene::init(m_canvas)) return; +#if ENABLE_RENDER_STATISTICS + auto start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_RENDER_STATISTICS + if (m_bed.get_shape().empty()) { // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE @@ -1617,8 +1625,15 @@ void GLCanvas3D::render() wxGetApp().imgui()->new_frame(); - // picking pass - _picking_pass(); + if (m_picking_enabled) + { + if (m_rectangle_selection.is_dragging()) + // picking pass using rectangle selection + _rectangular_selection_picking_pass(); + else + // regular picking pass + _picking_pass(); + } // draw scene glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); @@ -1654,6 +1669,9 @@ void GLCanvas3D::render() _render_camera_target(); #endif // ENABLE_SHOW_CAMERA_TARGET + if (m_picking_enabled && m_rectangle_selection.is_dragging()) + m_rectangle_selection.render(*this); + // draw overlays _render_gizmos_overlay(); _render_warning_texture(); @@ -1666,9 +1684,26 @@ void GLCanvas3D::render() if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f)) m_layers_editing.render_overlay(*this); +#if ENABLE_RENDER_STATISTICS + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.set_next_window_bg_alpha(0.5f); + imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui.text("Last frame: "); + ImGui::SameLine(); + imgui.text(std::to_string(m_render_stats.last_frame)); + ImGui::SameLine(); + imgui.text(" ms"); + imgui.end(); +#endif // ENABLE_RENDER_STATISTICS + wxGetApp().imgui()->render(); m_canvas->SwapBuffers(); + +#if ENABLE_RENDER_STATISTICS + auto end_time = std::chrono::high_resolution_clock::now(); + m_render_stats.last_frame = std::chrono::duration_cast(end_time - start_time).count(); +#endif // ENABLE_RENDER_STATISTICS } void GLCanvas3D::select_all() @@ -2347,9 +2382,51 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux post_event(SimpleEvent(EVT_GLCANVAS_TAB)); } + else if (keyCode == WXK_SHIFT) + { + if (m_picking_enabled && m_rectangle_selection.is_dragging()) + { + _update_selection_from_hover(); + m_rectangle_selection.stop_dragging(); + m_mouse.ignore_left_up = true; + m_dirty = true; + } +// set_cursor(Standard); + } + else if (keyCode == WXK_ALT) + { + if (m_picking_enabled && m_rectangle_selection.is_dragging()) + { + _update_selection_from_hover(); + m_rectangle_selection.stop_dragging(); + m_mouse.ignore_left_up = true; + m_dirty = true; + } +// set_cursor(Standard); + } + else if (keyCode == WXK_CONTROL) + m_dirty = true; } else if (evt.GetEventType() == wxEVT_KEY_DOWN) { m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers(); + if (keyCode == WXK_SHIFT) + { + if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) + { + m_mouse.ignore_left_up = false; +// set_cursor(Cross); + } + } + else if (keyCode == WXK_ALT) + { + if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) + { + m_mouse.ignore_left_up = false; +// set_cursor(Cross); + } + } + else if (keyCode == WXK_CONTROL) + m_dirty = true; } } } @@ -2468,6 +2545,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_3D_as_invalid(); m_mouse.set_start_position_2D_as_invalid(); m_mouse.dragging = false; + m_mouse.ignore_left_up = false; m_dirty = true; if (m_canvas->HasCapture()) @@ -2566,7 +2644,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (top_level_wnd && top_level_wnd->IsActive()) m_canvas->SetFocus(); m_mouse.position = pos.cast(); - // 1) forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while + // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to // change the volume hover state if any is under the mouse // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible, @@ -2606,26 +2684,35 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } } + else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) + { + if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) + { + m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); + m_dirty = true; + } + } else { // Select volume in this 3D canvas. // Don't deselect a volume if layer editing is enabled. We want the object to stay selected // during the scene manipulation. - if (m_picking_enabled && ((m_hover_volume_id != -1) || !is_layers_editing_enabled())) + if (m_picking_enabled && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) { - if (evt.LeftDown() && (m_hover_volume_id != -1)) + if (evt.LeftDown() && !m_hover_volume_idxs.empty()) { - bool already_selected = m_selection.contains_volume(m_hover_volume_id); + int volume_idx = get_first_hover_volume_idx(); + bool already_selected = m_selection.contains_volume(volume_idx); bool ctrl_down = evt.CmdDown(); Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); if (already_selected && ctrl_down) - m_selection.remove(m_hover_volume_id); + m_selection.remove(volume_idx); else { - m_selection.add(m_hover_volume_id, !ctrl_down, true); + m_selection.add(volume_idx, !ctrl_down, true); m_mouse.drag.move_requires_threshold = !already_selected; if (already_selected) m_mouse.set_move_start_threshold_position_2D_as_invalid(); @@ -2633,6 +2720,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.drag.move_start_threshold_position_2D = pos; } + // propagate event through callback if (curr_idxs != m_selection.get_volume_idxs()) { m_gizmos.refresh_on_off_state(m_selection); @@ -2643,18 +2731,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } - // propagate event through callback - if (m_hover_volume_id != -1) + if (!m_hover_volume_idxs.empty()) { if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -1)) { // Only accept the initial position, if it is inside the volume bounding box. - BoundingBoxf3 volume_bbox = m_volumes.volumes[m_hover_volume_id]->transformed_bounding_box(); + int volume_idx = get_first_hover_volume_idx(); + BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); volume_bbox.offset(1.0); if (volume_bbox.contains(m_mouse.scene_position)) { // The dragging operation is initiated. - m_mouse.drag.move_volume_idx = m_hover_volume_id; + m_mouse.drag.move_volume_idx = volume_idx; m_selection.start_dragging(); m_mouse.drag.start_position_3D = m_mouse.scene_position; m_moving = true; @@ -2671,7 +2759,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) Vec3d cur_pos = m_mouse.drag.start_position_3D; // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag - if (m_selection.contains_volume(m_hover_volume_id)) + if (m_selection.contains_volume(get_first_hover_volume_idx())) { if (m_camera.get_theta() == 90.0f) { @@ -2709,10 +2797,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_regenerate_volumes = false; m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); wxGetApp().obj_manipul()->update_settings_value(m_selection); - m_dirty = true; } } + else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) + { + m_rectangle_selection.dragging(pos.cast()); + m_dirty = true; + } else if (evt.Dragging()) { m_mouse.dragging = true; @@ -2726,7 +2818,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (evt.LeftIsDown()) { // if dragging over blank area with left button, rotate - if ((m_hover_volume_id == -1) && m_mouse.is_start_position_3D_defined()) + if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined()) { const Vec3d& orig = m_mouse.drag.start_position_3D; float sign = m_camera.inverted_phi ? -1.0f : 1.0f; @@ -2769,7 +2861,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); } - else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !is_layers_editing_enabled()) + else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging()) + { + if (evt.ShiftDown() || evt.AltDown()) + _update_selection_from_hover(); + + m_rectangle_selection.stop_dragging(); + } + else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled()) { // deselect and propagate event through callback if (!evt.ShiftDown() && m_picking_enabled) @@ -2788,18 +2887,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (evt.RightUp()) { m_mouse.position = pos.cast(); - // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while + // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while // the context menu is already shown render(); - if (m_hover_volume_id != -1) + if (!m_hover_volume_idxs.empty()) { // if right clicking on volume, propagate event through callback (shows context menu) - if (m_volumes.volumes[m_hover_volume_id]->hover - && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower // no context menu for the wipe tower + int volume_idx = get_first_hover_volume_idx(); + if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open { // forces the selection of the volume - m_selection.add(m_hover_volume_id); + m_selection.add(volume_idx); m_gizmos.refresh_on_off_state(m_selection); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_gizmos.update_data(*this); @@ -3199,6 +3298,20 @@ double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const return factor * m_bed.get_bounding_box().max_size(); } +void GLCanvas3D::set_cursor(ECursorType type) +{ + if ((m_canvas != nullptr) && (m_cursor_type != type)) + { + switch (type) + { + case Standard: { m_canvas->SetCursor(*wxSTANDARD_CURSOR); break; } + case Cross: { m_canvas->SetCursor(*wxCROSS_CURSOR); break; } + } + + m_cursor_type = type; + } +} + void GLCanvas3D::msw_rescale() { m_warning_texture.msw_rescale(*this); @@ -3569,10 +3682,10 @@ void GLCanvas3D::_refresh_if_shown_on_screen() void GLCanvas3D::_picking_pass() const { - const Vec2d& pos = m_mouse.position; - - if (m_picking_enabled && !m_mouse.dragging && (pos != Vec2d(DBL_MAX, DBL_MAX))) + if (m_picking_enabled && !m_mouse.dragging && (m_mouse.position != Vec2d(DBL_MAX, DBL_MAX))) { + m_hover_volume_idxs.clear(); + // Render the object for picking. // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. // Better to use software ray - casting on a bounding - box hierarchy. @@ -3603,27 +3716,98 @@ void GLCanvas3D::_picking_pass() const GLubyte color[4] = { 0, 0, 0, 0 }; const Size& cnv_size = get_canvas_size(); - bool inside = (0 <= pos(0)) && (pos(0) < cnv_size.get_width()) && (0 <= pos(1)) && (pos(1) < cnv_size.get_height()); + bool inside = (0 <= m_mouse.position(0)) && (m_mouse.position(0) < cnv_size.get_width()) && (0 <= m_mouse.position(1)) && (m_mouse.position(1) < cnv_size.get_height()); if (inside) { - glsafe(::glReadPixels(pos(0), cnv_size.get_height() - pos(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); - volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; + glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); + volume_id = color[0] + (color[1] << 8) + (color[2] << 16); } if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { - m_hover_volume_id = volume_id; + m_hover_volume_idxs.push_back(volume_id); m_gizmos.set_hover_id(-1); } else - { - m_hover_volume_id = -1; m_gizmos.set_hover_id(inside && volume_id <= GLGizmoBase::BASE_ID ? (GLGizmoBase::BASE_ID - volume_id) : -1); - } _update_volumes_hover_state(); } } +void GLCanvas3D::_rectangular_selection_picking_pass() const +{ + m_gizmos.set_hover_id(-1); + + std::set idxs; + + if (m_picking_enabled) + { + if (m_multisample_allowed) + glsafe(::glDisable(GL_MULTISAMPLE)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + + _render_volumes_for_picking(); + + if (m_multisample_allowed) + glsafe(::glEnable(GL_MULTISAMPLE)); + + int width = std::max((int)m_rectangle_selection.get_width(), 1); + int height = std::max((int)m_rectangle_selection.get_height(), 1); + int px_count = width * height; + + int left = (int)m_rectangle_selection.get_left(); + int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); + if ((left >= 0) && (top >= 0)) + { +#define USE_PARALLEL 1 +#if USE_PARALLEL + struct Pixel + { + std::array data; + int id() const { return data[0] + (data[1] << 8) + (data[2] << 16); } + }; + + std::vector frame(px_count); + glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); + + tbb::spin_mutex mutex; + tbb::parallel_for(tbb::blocked_range(0, frame.size(), (size_t)width), + [this, &frame, &idxs, &mutex](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) + { + int volume_id = frame[i].id(); + if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) + { + mutex.lock(); + idxs.insert(volume_id); + mutex.unlock(); + } + } + } + ); +#else + std::vector frame(4 * px_count); + glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); + + for (int i = 0; i < px_count; ++i) + { + int px_id = 4 * i; + int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16); + if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) + idxs.insert(volume_id); + } +#endif // USE_PARALLEL + } + } + + m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); + _update_volumes_hover_state(); +} + void GLCanvas3D::_render_background() const { glsafe(::glPushMatrix()); @@ -4109,24 +4293,93 @@ void GLCanvas3D::_update_volumes_hover_state() const { for (GLVolume* v : m_volumes.volumes) { - v->hover = false; + v->hover = GLVolume::HS_None; } - if (m_hover_volume_id == -1) + if (m_hover_volume_idxs.empty()) return; - GLVolume* volume = m_volumes.volumes[m_hover_volume_id]; - if (volume->is_modifier) - volume->hover = true; - else - { - int object_idx = volume->object_idx(); - int instance_idx = volume->instance_idx(); + bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); // additive select/deselect + bool shift_pressed = wxGetKeyState(WXK_SHIFT); // select by rectangle + bool alt_pressed = wxGetKeyState(WXK_ALT); // deselect by rectangle - for (GLVolume* v : m_volumes.volumes) + if (alt_pressed && (shift_pressed || ctrl_pressed)) + { + // illegal combinations of keys + m_hover_volume_idxs.clear(); + return; + } + + bool selection_modifiers_only = m_selection.is_empty() || m_selection.is_any_modifier(); + + bool hover_modifiers_only = true; + for (int i : m_hover_volume_idxs) + { + if (!m_volumes.volumes[i]->is_modifier) { - if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) - v->hover = true; + hover_modifiers_only = false; + break; + } + } + + std::set> hover_instances; + for (int i : m_hover_volume_idxs) + { + const GLVolume& v = *m_volumes.volumes[i]; + hover_instances.insert(std::make_pair(v.object_idx(), v.instance_idx())); + } + + bool hover_from_single_instance = hover_instances.size() == 1; + + if (hover_modifiers_only && !hover_from_single_instance) + { + // do not allow to select volumes from different instances + m_hover_volume_idxs.clear(); + return; + } + + for (int i : m_hover_volume_idxs) + { + GLVolume& volume = *m_volumes.volumes[i]; + if (volume.hover != GLVolume::HS_None) + continue; + + bool deselect = volume.selected && ((ctrl_pressed && !shift_pressed) || alt_pressed); + // (volume->is_modifier && !selection_modifiers_only && !is_ctrl_pressed) -> allows hovering on selected modifiers belonging to selection of type Instance + bool select = (!volume.selected || (volume.is_modifier && !selection_modifiers_only && !ctrl_pressed)) && !alt_pressed; + + if (select || deselect) + { + bool as_volume = + volume.is_modifier && hover_from_single_instance && !ctrl_pressed && + ( + (!deselect) || + (deselect && !m_selection.is_single_full_instance() && (volume.object_idx() == m_selection.get_object_idx()) && (volume.instance_idx() == m_selection.get_instance_idx())) + ); + + if (as_volume) + { + if (deselect) + volume.hover = GLVolume::HS_Deselect; + else + volume.hover = GLVolume::HS_Select; + } + else + { + int object_idx = volume.object_idx(); + int instance_idx = volume.instance_idx(); + + for (GLVolume* v : m_volumes.volumes) + { + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + { + if (deselect) + v->hover = GLVolume::HS_Deselect; + else + v->hover = GLVolume::HS_Select; + } + } + } } } } @@ -5405,6 +5658,55 @@ void GLCanvas3D::_resize_toolbars() const } #endif // !ENABLE_SVG_ICONS +void GLCanvas3D::_update_selection_from_hover() +{ + bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); + + if (m_hover_volume_idxs.empty()) + { + if (!ctrl_pressed && (m_rectangle_selection.get_state() == GLSelectionRectangle::Select)) + m_selection.clear(); + + return; + } + + GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); + + bool hover_modifiers_only = true; + for (int i : m_hover_volume_idxs) + { + if (!m_volumes.volumes[i]->is_modifier) + { + hover_modifiers_only = false; + break; + } + } + + if ((state == GLSelectionRectangle::Select) && !ctrl_pressed) + m_selection.clear(); + + for (int i : m_hover_volume_idxs) + { + if (state == GLSelectionRectangle::Select) + { + if (hover_modifiers_only) + { + const GLVolume& v = *m_volumes.volumes[i]; + m_selection.add_volume(v.object_idx(), v.volume_idx(), v.instance_idx(), false); + } + else + m_selection.add(i, false); + } + else + m_selection.remove(i); + } + + m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.update_data(*this); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + m_dirty = true; +} + const Print* GLCanvas3D::fff_print() const { return (m_process == nullptr) ? nullptr : m_process->fff_print(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 8abc0378e..4670b7221 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -303,6 +303,7 @@ class GLCanvas3D Vec2d position; Vec3d scene_position; Drag drag; + bool ignore_left_up; Mouse(); @@ -319,7 +320,6 @@ class GLCanvas3D } }; -private: struct SlaCap { struct Triangles @@ -399,6 +399,23 @@ private: void render(const GLCanvas3D& canvas) const; }; +#if ENABLE_RENDER_STATISTICS + struct RenderStats + { + long long last_frame; + + RenderStats() : last_frame(0) {} + }; +#endif // ENABLE_RENDER_STATISTICS + +public: + enum ECursorType : unsigned char + { + Standard, + Cross + }; + +private: wxGLCanvas* m_canvas; wxGLContext* m_context; #if ENABLE_RETINA_GL @@ -433,7 +450,7 @@ private: bool m_initialized; bool m_use_VBOs; bool m_apply_zoom_to_volumes_filter; - mutable int m_hover_volume_id; + mutable std::vector m_hover_volume_idxs; bool m_warning_texture_enabled; bool m_legend_texture_enabled; bool m_picking_enabled; @@ -443,6 +460,8 @@ private: bool m_regenerate_volumes; bool m_moving; bool m_tab_down; + ECursorType m_cursor_type; + GLSelectionRectangle m_rectangle_selection; // Following variable is obsolete and it should be safe to remove it. // I just don't want to do it now before a release (Lukas Matena 24.3.2019) @@ -454,6 +473,10 @@ private: GCodePreviewVolumeIndex m_gcode_preview_volume_index; +#if ENABLE_RENDER_STATISTICS + RenderStats m_render_stats; +#endif // ENABLE_RENDER_STATISTICS + public: GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); ~GLCanvas3D(); @@ -582,7 +605,7 @@ public: float get_view_toolbar_height() const { return m_view_toolbar.get_height(); } int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } - int get_hover_volume_id() const { return m_hover_volume_id; } + int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); @@ -594,6 +617,7 @@ public: double get_size_proportional_to_max_bed_size(double factor) const; + void set_cursor(ECursorType type); void msw_rescale(); private: @@ -612,6 +636,7 @@ private: void _refresh_if_shown_on_screen(); void _picking_pass() const; + void _rectangular_selection_picking_pass() const; void _render_background() const; void _render_bed(float theta) const; void _render_axes() const; @@ -690,6 +715,9 @@ private: void _resize_toolbars() const; #endif // !ENABLE_SVG_ICONS + // updates the selection from the content of m_hover_volume_idxs + void _update_selection_from_hover(); + static std::vector _parse_colors(const std::vector& colors); public: diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp new file mode 100644 index 000000000..9684bb5ec --- /dev/null +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -0,0 +1,117 @@ +#include "GLSelectionRectangle.hpp" +#include "Camera.hpp" +#include "3DScene.hpp" +#include "GLCanvas3D.hpp" + +#include + +namespace Slic3r { +namespace GUI { + + void GLSelectionRectangle::start_dragging(const Vec2d& mouse_position, EState state) + { + if (is_dragging() || (state == Off)) + return; + + m_state = state; + m_start_corner = mouse_position; + m_end_corner = mouse_position; + } + + void GLSelectionRectangle::dragging(const Vec2d& mouse_position) + { + if (!is_dragging()) + return; + + m_end_corner = mouse_position; + } + + std::vector GLSelectionRectangle::stop_dragging(const GLCanvas3D& canvas, const std::vector& points) + { + std::vector out; + + if (!is_dragging()) + return out; + + m_state = Off; + + const Camera& camera = canvas.get_camera(); + const std::array& viewport = camera.get_viewport(); + const Transform3d& modelview_matrix = camera.get_view_matrix(); + const Transform3d& projection_matrix = camera.get_projection_matrix(); + + // bounding box created from the rectangle corners - will take care of order of the corners + BoundingBox rectangle(Points{ Point(m_start_corner.cast()), Point(m_end_corner.cast()) }); + + // Iterate over all points and determine whether they're in the rectangle. + for (unsigned int i = 0; i stop_dragging(const GLCanvas3D& canvas, const std::vector& points); + + // Disables the rectangle. + void stop_dragging(); + + void render(const GLCanvas3D& canvas) const; + + bool is_dragging() const { return m_state != Off; } + EState get_state() const { return m_state; } + + float get_width() const { return std::abs(m_start_corner(0) - m_end_corner(0)); } + float get_height() const { return std::abs(m_start_corner(1) - m_end_corner(1)); } + float get_left() const { return std::min(m_start_corner(0), m_end_corner(0)); } + float get_right() const { return std::max(m_start_corner(0), m_end_corner(0)); } + float get_top() const { return std::max(m_start_corner(1), m_end_corner(1)); } + float get_bottom() const { return std::min(m_start_corner(1), m_end_corner(1)); } + +private: + EState m_state = Off; + Vec2d m_start_corner; + Vec2d m_end_corner; +}; + + +} // namespace GUI +} // namespace Slic3r + + +#endif // slic3r_GLGizmoSlaSupports_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index e8fe32688..5be5e9a89 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -100,7 +100,7 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const if (m_quadric != nullptr && selection.is_from_single_instance()) render_points(selection, false); - render_selection_rectangle(); + m_selection_rectangle.render(m_parent); render_clipping_plane(selection); glsafe(::glDisable(GL_BLEND)); @@ -240,52 +240,6 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const } - -void GLGizmoSlaSupports::render_selection_rectangle() const -{ - if (m_selection_rectangle_status == srOff) - return; - - glsafe(::glLineWidth(1.5f)); - float render_color[3] = {0.f, 1.f, 0.f}; - if (m_selection_rectangle_status == srDeselect) { - render_color[0] = 1.f; - render_color[1] = 0.3f; - render_color[2] = 0.3f; - } - glsafe(::glColor3fv(render_color)); - - glsafe(::glPushAttrib(GL_TRANSFORM_BIT)); // remember current MatrixMode - - glsafe(::glMatrixMode(GL_MODELVIEW)); // cache modelview matrix and set to identity - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - - glsafe(::glMatrixMode(GL_PROJECTION)); // cache projection matrix and set to identity - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - - glsafe(::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f)); // set projection matrix so that world coords = window coords - - // render the selection rectangle (window coordinates): - glsafe(::glPushAttrib(GL_ENABLE_BIT)); - glsafe(::glLineStipple(4, 0xAAAA)); - glsafe(::glEnable(GL_LINE_STIPPLE)); - - ::glBegin(GL_LINE_LOOP); - ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); - glsafe(::glEnd()); - glsafe(::glPopAttrib()); - - glsafe(::glPopMatrix()); // restore former projection matrix - glsafe(::glMatrixMode(GL_MODELVIEW)); - glsafe(::glPopMatrix()); // restore former modelview matrix - glsafe(::glPopAttrib()); // restore former MatrixMode -} - void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const { glsafe(::glEnable(GL_DEPTH_TEST)); @@ -513,11 +467,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { if (m_hover_id == -1) { if (shift_down || alt_down) { - m_selection_rectangle_status = shift_down ? srSelect : srDeselect; - m_selection_rectangle_start_corner = mouse_position; - m_selection_rectangle_end_corner = mouse_position; - m_canvas_width = m_parent.get_canvas_size().get_width(); - m_canvas_height = m_parent.get_canvas_size().get_height(); + m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); } } else { @@ -533,7 +483,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } // left down without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftDown && m_selection_rectangle_status == srOff && !shift_down) { + if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: if (m_hover_id != -1) return false; @@ -558,38 +508,36 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle_status != srOff) { - const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); - const Camera& camera = m_parent.get_camera(); - const std::array& viewport = camera.get_viewport(); - const Transform3d& modelview_matrix = camera.get_view_matrix(); - const Transform3d& projection_matrix = camera.get_projection_matrix(); + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { + // Is this a selection or deselection rectangle? + GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); + // First collect positions of all the points in world coordinates. + const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); + std::vector points; + for (unsigned int i=0; i()); + points.back()(2) += m_z_shift; + } + // Now ask the rectangle which of the points are inside. + const Camera& camera = m_parent.get_camera(); + std::vector selected_idxs = m_selection_rectangle.stop_dragging(m_parent, points); + + // we'll recover current look direction (in world coords) and transform it to model coords. const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - - // bounding box created from the rectangle corners - will take care of order of the corners - BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast()), Point(m_selection_rectangle_end_corner.cast())}); - const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true); - - // we'll recover current look direction from the modelview matrix (in world coords)... Vec3f direction_to_camera = camera.get_dir_forward().cast(); - // ...and transform it to model coords. Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval(); Vec3f scaling = volume->get_instance_scaling_factor().cast(); direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); - // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: - for (unsigned int i=0; i() * support_point.pos; - pos(2) += m_z_shift; - GLdouble out_x, out_y, out_z; - ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), (GLdouble*)modelview_matrix.data(), (GLdouble*)projection_matrix.data(), (GLint*)viewport.data(), &out_x, &out_y, &out_z); - out_y = m_canvas_height - out_y; - - if (rectangle.contains(Point(out_x, out_y)) && !is_point_clipped(support_point.pos.cast())) { + if (!is_point_clipped(support_point.pos.cast())) { bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: std::vector hits; @@ -627,14 +575,13 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } if (!is_obscured) { - if (m_selection_rectangle_status == srDeselect) + if (rectangle_status == GLSelectionRectangle::Deselect) unselect_point(i); else select_point(i); } } } - m_selection_rectangle_status = srOff; return true; } @@ -652,9 +599,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return true; // point has been placed and the button not released yet // this prevents GLCanvas from starting scene rotation - if (m_selection_rectangle_status != srOff) { - m_selection_rectangle_end_corner = mouse_position; - m_selection_rectangle_status = shift_down ? srSelect : srDeselect; + if (m_selection_rectangle.is_dragging()) { + m_selection_rectangle.dragging(mouse_position); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index eaf0932c4..7e09b04ac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -3,6 +3,7 @@ #include "GLGizmoBase.hpp" #include "GLGizmos.hpp" +#include "slic3r/GUI/GLSelectionRectangle.hpp" // There is an L function in igl that would be overridden by our localization macro - let's undefine it... #undef L @@ -67,13 +68,16 @@ public: void delete_selected_points(bool force = false); ClippingPlane get_sla_clipping_plane() const; + bool is_in_editing_mode() const { return m_editing_mode; } + bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); } + private: bool on_init(); void on_update(const UpdateData& data, const Selection& selection); virtual void on_render(const Selection& selection) const; virtual void on_render_for_picking(const Selection& selection) const; - void render_selection_rectangle() const; + //void render_selection_rectangle() const; void render_points(const Selection& selection, bool picking = false) const; void render_clipping_plane(const Selection& selection) const; bool is_mesh_update_necessary() const; @@ -91,20 +95,12 @@ private: mutable Vec3d m_old_clipping_plane_normal; mutable Vec3d m_clipping_plane_normal = Vec3d::Zero(); - enum SelectionRectangleStatus { - srOff = 0, - srSelect = 1, - srDeselect = 2 - }m_selection_rectangle_status = srOff; + GLSelectionRectangle m_selection_rectangle; - Vec2d m_selection_rectangle_start_corner; - Vec2d m_selection_rectangle_end_corner; bool m_wait_for_up_event = false; bool m_unsaved_changes = false; // Are there unsaved changes in manual mode? bool m_selection_empty = true; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) - int m_canvas_width; - int m_canvas_height; mutable std::unique_ptr m_tms; mutable std::unique_ptr m_supports_tms; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 5e37e42ad..a00303634 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -693,7 +693,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); processed = true; } - else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_hover_volume_id() != -1) || grabber_contains_mouse())) + else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) { // to avoid to loose the selection when user clicks an object while the Flatten gizmo is active processed = true; @@ -843,13 +843,34 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas) if (evt.GetEventType() == wxEVT_KEY_UP) { - if ((m_current == SlaSupports) && (keyCode == WXK_SHIFT) && gizmo_event(SLAGizmoEventType::ShiftUp)) - // shift has been just released - SLA gizmo might want to close rectangular selection. - processed = true; + if (m_current == SlaSupports) + { + GLGizmoSlaSupports* gizmo = reinterpret_cast(get_current()); - if ((m_current == SlaSupports) && (keyCode == WXK_ALT) && gizmo_event(SLAGizmoEventType::AltUp)) - // alt has been just released - SLA gizmo might want to close rectangular selection. + if (keyCode == WXK_SHIFT) + { + // shift has been just released - SLA gizmo might want to close rectangular selection. + if (gizmo_event(SLAGizmoEventType::ShiftUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging())) + processed = true; + } + else if (keyCode == WXK_ALT) + { + // alt has been just released - SLA gizmo might want to close rectangular selection. + if (gizmo_event(SLAGizmoEventType::AltUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging())) + processed = true; + } + } + +// if (processed) +// canvas.set_cursor(GLCanvas3D::Standard); + } + else if (evt.GetEventType() == wxEVT_KEY_DOWN) + { + if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && reinterpret_cast(get_current())->is_in_editing_mode()) + { +// canvas.set_cursor(GLCanvas3D::Cross); processed = true; + } } if (processed)