diff --git a/docs/HLSD/focus-mode.md b/docs/HLSD/focus-mode.md new file mode 100644 index 0000000000..d877286251 --- /dev/null +++ b/docs/HLSD/focus-mode.md @@ -0,0 +1,66 @@ +# Focus mode (hiding inactive plates) — High Level Design + +## Purpose and scope + +"Focus mode" is an entry of the canvas menu in the bottom left corner of Prepare. Turned on (eye +lit), the 3D view hides the inactive plates and shows the current plate alone, so a project with +many plates can be worked on one plate at a time. Turned off, every plate is shown. It is a view +setting: it changes nothing in the model, the slice or the project file, and it does not reach +plate thumbnails. The choice is kept in the app config as `focus_mode`, off by default. + +Preview needs no such option, as it already draws the current plate alone and has its own plate +toolbar. The assembly view and the Design tab do not lay objects out on plates, so the entry is +disabled there. + +## What is shown + +The current plate is drawn with its bed, label and icons; the other plates are not drawn at all. The +world axes, which Prepare draws at the corner of the first plate, move to the corner of the current +plate, where Preview draws them. + +An object instance is shown when its footprint, the XY bounding box of the convex hulls of its +volumes, overlaps the current plate, or when it is selected. The test is made per instance, so the +parts of an object are shown or hidden together, and a wipe tower follows its plate the same way. +It is geometric rather than the plate membership `PartPlateList` keeps, because an instance stays on +the plate it was first placed on: an object of another plate that reaches over this one, or one too +large for any plate, would otherwise be invisible while it takes room on the current plate. A +selected instance stays visible so that what a selection or a gizmo acts on is never hidden, for +instance an object picked in the object list on another plate. + +## Hiding + +A hidden object is neither drawn nor picked. `GLCanvas3D::_update_inactive_plates_visibility()` +runs at the start of each frame of Prepare. It clears `GLVolume::is_active` on the volumes of the +hidden instances and deactivates their raycasters, which every render pass, the hover and rectangle +selection, the shadow and center of mass markers already respect. It also deactivates the raycasters +of the other plates, whose icons would otherwise still take clicks where nothing is drawn. The state +persists between frames, so the raycasts made while handling the mouse, such as the anchor of a pan, +see the same scene as the frame. + +Other features clear `is_active` too: gizmos hide the objects they do not edit and show them all +again when they close. `GLVolume::on_hidden_plate` records which volumes this update hid, so it only +shows again what it hid itself, and it waits for a gizmo that hides objects to close before doing +so. A volume another feature made active again is hidden again on the next frame. While Focus mode +is off and nothing is left hidden, the update returns at once. + +## Switching plates + +The other plates cannot be clicked while Focus mode hides them, so a compact counterpart of the Preview +plate toolbar takes their place: a column of plate thumbnails in the top left corner, below the top +toolbar, shown when the project has more than one plate and no gizmo is open. The thumbnails are the +ones the Preview toolbar shows (`PartPlate::thumbnail_data`); the canvas rebuilds its textures when +`Plater::plate_toolbar_image_version()` changes, a counter each consumer tracks on its own so that the +Preview toolbar's dirty flag is left alone. That counter moves after almost every edit, so the +thumbnail of the current plate, the only one edited while the others are hidden, is drawn again each +time, and the other plates' only once an edit to them has invalidated theirs. The current plate has an +accent border, a hovered one the gray border of the Preview toolbar, empty plates are dimmed and the +plate number sits in the corner; the tooltip gives the plate name. A click selects the plate and +clears the selection, as a click on a plate row of the object list does. The column scrolls when it +does not fit above the 3D navigator or the canvas toolbar. Its scrollbar works as the Preview +toolbar's, with which it shares its style: it shows only while the mouse is over the column or drags +the bar, and the mouse wheel scrolls the column either way. + +Whatever selects a plate while the others are hidden, the camera moves its target over the new plate +and keeps its angle, distance and height (`PartPlateList::select_plate()`), so the plate comes into +view where the previous one was. A zoom to all plates, as after loading a project or adding a plate, +frames the current plate instead. Turning Focus mode on does the same, so the current plate fills the view. diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 0156751d60..98efc032b8 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -399,6 +399,9 @@ void AppConfig::set_defaults() if (get("show_plate_gridlines").empty()) set_bool("show_plate_gridlines", true); + if (get("focus_mode").empty()) + set_bool("focus_mode", false); + if (get("show_outline").empty()) set_bool("show_outline", false); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 5a39f9a279..acd2e4f41c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -297,6 +297,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) , printable(true) , visible(true) , is_active(true) + , on_hidden_plate(false) , zoom_to_volumes(true) , shader_outside_printer_detection_enabled(false) , is_outside(false) @@ -824,6 +825,8 @@ bool GLVolume::is_below_printbed() const void GLVolume::render_sinking_contours() { + if (!is_active) + return; m_sinking_contours.render(); } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 664cd109cf..7a19b20a24 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -211,6 +211,8 @@ public: bool visible : 1; // Whether or not this volume is active for rendering bool is_active : 1; + // Whether or not is_active was cleared because the other plates are hidden + bool on_hidden_plate : 1; // Whether or not to use this volume when applying zoom_to_volumes() bool zoom_to_volumes : 1; // Wheter or not this volume is enabled for outside print volume detection in shader. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 73bd498656..bf87241fb6 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1679,6 +1679,7 @@ GLCanvas3D::~GLCanvas3D() m_sel_plate_toolbar.del_all_item(); m_sel_plate_toolbar.del_stats_item(); + m_plate_switcher_items.clear(); } void GLCanvas3D::post_event(wxEvent &&event) @@ -2360,6 +2361,8 @@ void GLCanvas3D::zoom_to_gcode() void GLCanvas3D::zoom_to_plate(int plate_idx) { BoundingBoxf3 box; + if (plate_idx == REQUIRES_ZOOM_TO_ALL_PLATE && _hides_inactive_plates()) + plate_idx = REQUIRES_ZOOM_TO_CUR_PLATE; if (plate_idx == REQUIRES_ZOOM_TO_ALL_PLATE) { box = wxGetApp().plater()->get_partplate_list().get_bounding_box(); box.min.z() = 0.0; @@ -2416,12 +2419,22 @@ float GLCanvas3D::get_collapse_toolbar_height() const } // The bottom-left corner: the 3D navigator, then the column of round canvas buttons beside it, in -// units of the toolbar scale. Shared by _render_3d_navigator, _render_canvas_toolbar and -// get_canvas_toolbar_right, so an overlay kept clear of the corner follows its layout. +// units of the toolbar scale. Shared by _render_3d_navigator, _render_canvas_toolbar, +// get_canvas_toolbar_right and canvas_corner_height, so an overlay kept clear of the corner follows its layout. static constexpr float NAVIGATOR_SIZE = 128.f; static constexpr float CANVAS_TOOLBAR_MARGIN = 10.f; // off the canvas edge, when there is no navigator static constexpr float CANVAS_TOOLBAR_PADDING = 2.f; static constexpr float CANVAS_TOOLBAR_BUTTON = 36.f; +static constexpr float CANVAS_TOOLBAR_SPACING = 6.f; + +// What the corner takes at the left edge of the canvas: the 3D navigator, or else the canvas toolbar. +static float canvas_corner_height() +{ + if (wxGetApp().show_3d_navigator()) + return NAVIGATOR_SIZE; + const int buttons = wxGetApp().show_canvas_zoom_button() ? 3 : 2; + return CANVAS_TOOLBAR_MARGIN + 2.f * CANVAS_TOOLBAR_PADDING + buttons * CANVAS_TOOLBAR_BUTTON + (buttons - 1) * CANVAS_TOOLBAR_SPACING; +} float GLCanvas3D::get_canvas_toolbar_right() const { @@ -2546,6 +2559,9 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init) wxGetApp().imgui()->new_frame(); + if (_update_inactive_plates_visibility()) + scene_dirty = true; + if (m_picking_enabled && !m_benchmarking) { if (m_rectangle_selection.is_dragging()) // picking pass using rectangle selection @@ -7669,7 +7685,6 @@ void GLCanvas3D::_update_select_plate_toolbar_stats_item(bool force_selected) { bool GLCanvas3D::_update_imgui_select_plate_toolbar() { - bool result = true; if (!m_sel_plate_toolbar.is_enabled()) { return false; } @@ -7694,19 +7709,13 @@ bool GLCanvas3D::_update_imgui_select_plate_toolbar() PartPlateList& plate_list = p_plater->get_partplate_list(); for (int i = 0; i < plate_list.get_plate_count(); i++) { IMToolbarItem* item = new IMToolbarItem(); - PartPlate* plate = plate_list.get_plate(i); - if (plate && plate->thumbnail_data.is_valid()) { - PartPlate* plate = plate_list.get_plate(i); - item->image_data = plate->thumbnail_data.pixels; - item->image_width = plate->thumbnail_data.width; - item->image_height = plate->thumbnail_data.height; - result = item->generate_texture(); - } + if (PartPlate* plate = plate_list.get_plate(i)) + item->set_thumbnail(plate->thumbnail_data); m_sel_plate_toolbar.m_items.push_back(item); } p_plater->clear_plate_toolbar_image_dirty(); m_sel_plate_toolbar.is_display_scrollbar = false; - return result; + return true; } //BBS: GUI refactor @@ -8901,11 +8910,75 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& const Vec2d bc = m_bed.build_volume().bed_center(); m_bed.set_axes_origin(Vec3d(bc.x(), bc.y(), 0.0)); } else { - m_bed.set_axes_mode(m_main_toolbar.is_enabled()); + // With the other plates hidden, the axes go to the current plate as in Preview. + m_bed.set_axes_mode(m_main_toolbar.is_enabled() && !_hides_inactive_plates()); } m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes); } +bool GLCanvas3D::_hides_inactive_plates() const +{ + return m_canvas_type == ECanvasType::CanvasView3D && !m_design_canvas && wxGetApp().focus_mode(); +} + +bool GLCanvas3D::_update_inactive_plates_visibility() +{ + const bool hide = _hides_inactive_plates(); + if (!hide && !m_inactive_plates_hidden) + return false; + + PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list(); + PartPlate* plate = hide ? plate_list.get_curr_plate() : nullptr; + + struct Instance + { + BoundingBoxf3 box; + bool selected{ false }; + }; + std::map, Instance> instances; + if (plate != nullptr) + for (const GLVolume* volume : m_volumes.volumes) { + Instance& instance = instances[{ volume->object_idx(), volume->instance_idx() }]; + instance.box.merge(volume->transformed_convex_hull_bounding_box()); + instance.selected |= volume->selected; + } + + // A gizmo that hides the other objects shows them all again when it closes. + const bool keep_hidden = m_gizmos.is_hiding_instances(); + bool changed = false; + bool any_hidden = false; + std::vector shown_again(m_volumes.volumes.size(), false); + for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { + GLVolume* volume = m_volumes.volumes[i]; + const Instance* instance = plate != nullptr ? &instances[{ volume->object_idx(), volume->instance_idx() }] : nullptr; + if (instance != nullptr && !instance->selected && instance->box.defined && !plate->get_bounding_box().overlap(instance->box)) { + changed |= volume->is_active; + volume->is_active = false; + volume->on_hidden_plate = true; + } else if (volume->on_hidden_plate && !keep_hidden) { + volume->is_active = true; + volume->on_hidden_plate = false; + shown_again[i] = true; + changed = true; + } + any_hidden |= volume->on_hidden_plate; + } + + if (auto* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume)) + for (const std::shared_ptr& item : *raycasters) { + const int i = SceneRaycaster::decode_id(SceneRaycaster::EType::Volume, item->get_id()); + if (0 <= i && i < int(m_volumes.volumes.size()) && (m_volumes.volumes[i]->on_hidden_plate || shown_again[i])) + item->set_active(shown_again[i]); + } + if (auto* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Bed)) + for (const std::shared_ptr& item : *raycasters) + item->set_active(!hide || SceneRaycaster::decode_id(SceneRaycaster::EType::Bed, item->get_id()) / PartPlate::GRABBER_COUNT == + plate_list.get_curr_plate_index()); + + m_inactive_plates_hidden = hide || any_hidden; + return changed; +} + void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid) { PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list(); @@ -8914,7 +8987,7 @@ void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transfo PartPlate* curr_plate = plate_list.get_curr_plate(); const Transform3d plate_view_matrix = m_design_canvas && curr_plate != nullptr ? Transform3d(view_matrix * Geometry::translation_transform(-curr_plate->get_origin())) : view_matrix; - plate_list.render(plate_view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid, !m_plate_chrome_enabled); + plate_list.render(plate_view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid, !m_plate_chrome_enabled, _hides_inactive_plates()); } BoundingBoxf3 GLCanvas3D::_current_plate_box() const @@ -9932,6 +10005,7 @@ void GLCanvas3D::_render_overlays() //BBS: GUI refactor: GLToolbar _render_imgui_select_plate_toolbar(); + _render_plate_switcher(); _render_return_toolbar(); // BBS //_render_view_toolbar(); @@ -10200,6 +10274,35 @@ void GLCanvas3D::_render_main_toolbar() m_main_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); } +// The scrollbar of the plate toolbars of Preview and Prepare, in units of the toolbar scale. +static constexpr float PLATE_TOOLBAR_SCROLLBAR = 14.f; + +// Pushes 4 colors and 1 style var. +static void push_plate_toolbar_scrollbar_style(bool is_dark, float scrollbar_size) +{ + const ImVec4 grab(0.77f, 0.77f, 0.77f, is_dark ? .6f : 1.f); + // A background with opacity over the window's would make a second color. + ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.f, 0.f, 0.f, 0.f)); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, grab); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, grab); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, grab); + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, scrollbar_size); +} + +// The border of a hovered plate in the plate toolbars, when it is not the current one. +static ImVec4 plate_toolbar_hover_color(bool is_dark) +{ + return ImVec4(0.67f, 0.67f, 0.67f, is_dark ? .6f : 1.f); +} + +// Not IsWindowHovered(), which is false while a button is held down and would drop the bar. +static bool plate_toolbar_keeps_scrollbar(bool shown, float scrollbar_size) +{ + const ImVec2 pos = ImGui::GetWindowPos(); + return ImGui::IsMouseHoveringRect(pos, pos + ImGui::GetWindowSize() + ImVec2(shown ? scrollbar_size : 0.f, 0.f), !shown) || + (ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0 && GImGui->ActiveIdWindow == ImGui::GetCurrentWindow()); +} + //BBS: GUI refactor: GLToolbar adjust //when rendering, {0, 0} is at the center, {-0.5, 0.5} at the left-up void GLCanvas3D::_render_imgui_select_plate_toolbar() @@ -10330,16 +10433,15 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() int item_count = m_sel_plate_toolbar.m_items.size() + (m_sel_plate_toolbar.show_stats_item ? 1 : 0); float window_height_calc = (item_count * (button_height + (margin_size + button_margin) * 2.0f) + (item_count - 1) * ImGui::GetStyle().ItemSpacing.y + ImGui::GetStyle().WindowPadding.y * 2.0f); bool show_scroll = m_sel_plate_toolbar.is_display_scrollbar && (window_height_calc > window_height_max); - float scrollbar_size = 14.0f * f_scale; + float scrollbar_size = PLATE_TOOLBAR_SCROLLBAR * f_scale; float window_height = std::min(window_height_calc, window_height_max); float window_width = button_width + (margin_size + button_margin + ImGui::GetStyle().WindowPadding.x) * 2 + (show_scroll ? scrollbar_size : 0); ImVec4 window_bg = m_is_dark ? ImVec4(.13f, .13f, .15f, .5f) : ImVec4(1.f, 1.f, 1.f, .7f); ImVec4 button_active = ImGuiWrapper::COL_ORCA; // ORCA: Use orca color for selected sliced plate border - ImVec4 button_hover = ImVec4(0.67f, 0.67f, 0.67, m_is_dark ? .6f : 1.0f); + ImVec4 button_hover = plate_toolbar_hover_color(m_is_dark); ImVec4 orca_active = m_is_dark ? ImGuiWrapper::COL_ORCA_DARK : ImGuiWrapper::COL_ORCA; ImVec4 orca_hover = m_is_dark ? ImGuiWrapper::COL_ORCA_HOVER_DARK : ImGuiWrapper::COL_ORCA_HOVER; - ImVec4 scroll_col = ImVec4(0.77f, 0.77f, 0.77f, m_is_dark ? .6f : 1.0f); ImU32 plate_bg = m_is_dark ? IM_COL32(255, 255, 255, 10) : IM_COL32(0, 0, 0, 10); ImU32 plate_dim = m_is_dark ? IM_COL32(30, 30, 30, 100) : IM_COL32(0, 0, 0, 50); float button_radius = 1.0f * f_scale; @@ -10347,14 +10449,10 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() //use white text as the background switch to black ImGui::PushStyleColor(ImGuiCol_Text, m_is_dark ? ImVec4(.9f, .9f, .9f, 1) : ImVec4(.3f, .3f, .3f, 1)); // ORCA Plate number text > Add support for dark mode ImGui::PushStyleColor(ImGuiCol_WindowBg, window_bg); - ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.f, 0.f, 0.f, 0.f)); // ORCA using background color with opacity creates a second color. This prevents secondary color - ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, scroll_col); - ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, scroll_col); - ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, scroll_col); ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_active); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_hover); + push_plate_toolbar_scrollbar_style(m_is_dark, scrollbar_size); - ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, scrollbar_size); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, show_scroll ? (6.f * f_scale) : (button_radius + margin_size + frame_padding + ImGui::GetStyle().WindowPadding.x)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, button_radius + margin_size); @@ -10625,18 +10723,109 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() ImGui::PopStyleColor(8); ImGui::PopStyleVar(7); - //ORCA ImGui::IsWindowHovered() returns false when left_down events on buttons that causes scrollbar disappears for a short time - auto win_pos = ImGui::GetWindowPos(); - bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll); - - // Also show scrollbar visible and continue to capture mouse position - const bool is_scrollbar_active_drag = GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0 && GImGui->ActiveIdWindow == ImGui::GetCurrentWindow(); - - m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered || is_scrollbar_active_drag; + m_sel_plate_toolbar.is_display_scrollbar = plate_toolbar_keeps_scrollbar(show_scroll, scrollbar_size); imgui.end(); } +void GLCanvas3D::_render_plate_switcher() +{ + Plater* plater = wxGetApp().plater(); + PartPlateList& plate_list = plater->get_partplate_list(); + const int plate_count = plate_list.get_plate_count(); + if (!_hides_inactive_plates() || plate_count < 2 || m_gizmos.get_current_type() != GLGizmosManager::Undefined) + return; + + // The thumbnails are the ones of the Preview plate toolbar. + const unsigned version = plater->plate_toolbar_image_version(); + if (m_plate_switcher_version != version || m_plate_switcher_items.size() != size_t(plate_count)) { + // Only the current plate is edited here; the others are redrawn when their thumbnails go invalid. + for (int i = 0; i < plate_count && !plater->is_gcode_3mf(); ++i) + plater->update_plate_thumbnails(i, i == plate_list.get_curr_plate_index()); + m_plate_switcher_items.clear(); + for (int i = 0; i < plate_count; ++i) { + m_plate_switcher_items.push_back(std::make_unique()); + m_plate_switcher_items.back()->set_thumbnail(plate_list.get_plate(i)->thumbnail_data); + } + m_plate_switcher_version = version; + } + + float sc = get_scale(); +#ifdef WIN32 + sc *= (float) get_dpi_for_window(wxGetApp().GetTopWindow()) / (float) DPI_DEFAULT; +#endif // WIN32 + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + const ImVec2 btn_size = ImVec2(72.f, 72.f) * sc; + const ImVec2 padding = ImVec2(8.f, 8.f) * sc; + const float spacing = 8.f * sc; + const float rounding = 4.f * sc; + const float scrollbar = PLATE_TOOLBAR_SCROLLBAR * sc; + const float top = std::max(m_main_toolbar.get_height(), m_gizmos.get_scaled_total_height()) + CANVAS_TOOLBAR_MARGIN * sc; + const float content_h = plate_count * (btn_size.y + spacing) - spacing + 2.f * padding.y; + const float max_h = std::max(btn_size.y + 2.f * padding.y, float(get_canvas_size().get_height()) - top - (canvas_corner_height() + CANVAS_TOOLBAR_MARGIN) * sc); + // As in the Preview plate toolbar, the scrollbar shows only while the mouse is over the column. + const bool scroll = m_plate_switcher_scrollbar && content_h > max_h; + + const ImVec4 text_color = m_is_dark ? ImVec4(.9f, .9f, .9f, 1.f) : ImVec4(.3f, .3f, .3f, 1.f); + const ImVec4 empty_color = ImVec4(text_color.x, text_color.y, text_color.z, .45f); + const ImVec4 hover = plate_toolbar_hover_color(m_is_dark); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + // Concentric with the thumbnails; tighter around the scrollbar, as in the Preview plate toolbar. + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, scroll ? 6.f * sc : rounding + padding.x); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, m_is_dark ? ImVec4(.13f, .13f, .15f, .5f) : ImVec4(1.f, 1.f, 1.f, .7f)); + push_plate_toolbar_scrollbar_style(m_is_dark, scrollbar); + + imgui.set_next_window_pos(CANVAS_TOOLBAR_MARGIN * sc, top, ImGuiCond_Always, 0.f, 0.f); + imgui.set_next_window_size(btn_size.x + 2.f * padding.x + (scroll ? scrollbar : 0.f), std::min(content_h, max_h), ImGuiCond_Always); + imgui.begin(std::string("##PlateSwitcher"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoCollapse | (scroll ? 0 : ImGuiWindowFlags_NoScrollbar)); + + const int curr_plate = plate_list.get_curr_plate_index(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImU32 plate_bg = m_is_dark ? IM_COL32(255, 255, 255, 10) : IM_COL32(0, 0, 0, 10); + const ImU32 plate_dim = m_is_dark ? IM_COL32(30, 30, 30, 100) : IM_COL32(0, 0, 0, 50); + for (int i = 0; i < plate_count; ++i) { + PartPlate* plate = plate_list.get_plate(i); + const bool current = i == curr_plate; + const ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::PushID(i); + const bool clicked = ImGui::InvisibleButton("##plate", btn_size); + ImGui::PopID(); + const bool hovered = ImGui::IsItemHovered(); + + draw_list->AddRectFilled(pos, pos + btn_size, plate_bg, rounding); + if (ImTextureID texture = m_plate_switcher_items[i]->texture_id) + draw_list->AddImageRounded(texture, pos, pos + btn_size, ImVec2(0.f, 1.f), ImVec2(1.f, 0.f), IM_COL32_WHITE, rounding); + if (plate->empty()) + draw_list->AddRectFilled(pos, pos + btn_size, plate_dim, rounding); + if (current || hovered) { + const float border = 2.f * sc; + draw_list->AddRect(pos - ImVec2(border, border) * .5f, pos + btn_size + ImVec2(border, border) * .5f, + ImGui::GetColorU32(current ? (hovered ? ImGuiWrapper::COL_ORCA_HOVER : ImGuiWrapper::COL_ORCA) : hover), + rounding + border * .5f, 0, border); + } + draw_list->AddText(pos + ImVec2(4.f, 2.f) * sc, ImGui::GetColorU32(plate->empty() ? empty_color : text_color), std::to_string(i + 1).c_str()); + + if (clicked && !current) + plater->CallAfter([i]() { + wxGetApp().plater()->select_plate(i); + wxGetApp().plater()->deselect_all(); + }); + if (hovered) { + const std::string name = plate->get_plate_name(); + imgui.tooltip(_u8L("Plate") + " " + std::to_string(i + 1) + (name.empty() ? "" : " (" + name + ")"), ImGui::GetFontSize() * 20.f); + } + } + + m_plate_switcher_scrollbar = plate_toolbar_keeps_scrollbar(scroll, scrollbar); + imgui.end(); + ImGui::PopStyleColor(5); + ImGui::PopStyleVar(5); +} + //BBS: GUI refactor: GLToolbar adjust //when rendering, {0, 0} is at the center, {-0.5, 0.5} at the left-up void GLCanvas3D::_render_assemble_view_toolbar() const @@ -10743,7 +10932,7 @@ void GLCanvas3D::_render_canvas_toolbar() ImVec2 btn_size = ImVec2(CANVAS_TOOLBAR_BUTTON, CANVAS_TOOLBAR_BUTTON) * sc; ImVec2 margin = ImVec2(m_canvas_toolbar_pos[0] > 0 ? 0.f : (CANVAS_TOOLBAR_MARGIN * sc), CANVAS_TOOLBAR_MARGIN * sc); - ImVec2 spacing = ImVec2(6.f, 6.f) * sc; + ImVec2 spacing = ImVec2(CANVAS_TOOLBAR_SPACING, CANVAS_TOOLBAR_SPACING) * sc; ImVec2 padding = ImVec2(CANVAS_TOOLBAR_PADDING, CANVAS_TOOLBAR_PADDING) * sc; Vec2i32 pos = { m_canvas_toolbar_pos[0] + margin.x, @@ -10946,6 +11135,17 @@ void GLCanvas3D::_render_canvas_toolbar() []{wxGetApp().toggle_show_plate_gridlines();} ); + create_menu_item( _utf8(L("Focus mode")), + m_canvas_type == ECanvasType::CanvasView3D && !m_design_canvas, // work only on prepare + wxGetApp().focus_mode(), + [this]{ + wxGetApp().toggle_focus_mode(); + if (wxGetApp().focus_mode()) + zoom_to_plate(REQUIRES_ZOOM_TO_CUR_PLATE); + m_dirty = true; + } + ); + ImGui::Separator(); create_menu_item( _utf8(L("Labels")), diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index df265bb080..e483859824 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -660,6 +660,10 @@ private: mutable GLToolbar m_main_toolbar; mutable GLToolbar m_separator_toolbar; mutable IMToolbar m_sel_plate_toolbar; + // Thumbnails of the plate switcher (Focus mode), refreshed when the plater's version changes. + std::vector> m_plate_switcher_items; + unsigned m_plate_switcher_version{ unsigned(-1) }; + bool m_plate_switcher_scrollbar{ false }; mutable GLToolbar m_assemble_view_toolbar; mutable IMReturnToolbar m_return_toolbar; mutable Vec2i32 m_canvas_toolbar_pos = {140, 5}; @@ -778,6 +782,8 @@ private: //BBS:add plate related logic mutable std::vector m_hover_volume_idxs; std::vector m_hover_plate_idxs; + // Some volume or plate raycaster may still be hidden with the inactive plates. + bool m_inactive_plates_hidden{ false }; // IMEX ghost hover state (plate-owned transparent ghost volumes). int m_hover_ghost_head { -1 }; // physical head index, -1 when not hovering a ghost int m_hover_ghost_plate { -1 }; // plate index for the hovered ghost, -1 when none @@ -1527,6 +1533,9 @@ private: void _render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix); //BBS: add part plate related logic void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true); + bool _hides_inactive_plates() const; + // Returns whether what the scene shows changed. + bool _update_inactive_plates_visibility(); // The current plate's box (XY, at z = 0); in the Design tab, its own bed's. BoundingBoxf3 _current_plate_box() const; // Design tab: draw the CAD grid (minor 10 mm + major 50 mm) in place of the plate's @@ -1565,6 +1574,8 @@ private: void _render_gizmos_overlay(); void _render_main_toolbar(); void _render_imgui_select_plate_toolbar(); + // Prepare: picks the plate to show while the inactive ones are hidden. + void _render_plate_switcher(); void _render_assemble_view_toolbar() const; void _render_return_toolbar() const; void _render_canvas_toolbar(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index a40c4398d1..475bec7e26 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -436,6 +436,8 @@ public: bool show_plate_gridlines() const { return app_config->get_bool("show_plate_gridlines"); } void toggle_show_plate_gridlines() const { app_config->set_bool("show_plate_gridlines", !show_plate_gridlines()); } + bool focus_mode() const { return app_config->get_bool("focus_mode"); } + void toggle_focus_mode() const { app_config->set_bool("focus_mode", !focus_mode()); } bool show_canvas_zoom_button() const { return app_config->get_bool("show_canvas_zoom_button"); } void toggle_canvas_zoom_button() const { app_config->set_bool("show_canvas_zoom_button", !show_canvas_zoom_button()); } diff --git a/src/slic3r/GUI/IMToolbar.cpp b/src/slic3r/GUI/IMToolbar.cpp index 1629644df9..6a885f473f 100644 --- a/src/slic3r/GUI/IMToolbar.cpp +++ b/src/slic3r/GUI/IMToolbar.cpp @@ -23,27 +23,23 @@ IMToolbarItem::~IMToolbarItem() } -bool IMToolbarItem::generate_texture() +void IMToolbarItem::set_thumbnail(const ThumbnailData& thumbnail) { - GLint last_texture; - unsigned m_image_texture{ 0 }; - unsigned char* pixels = (unsigned char*)(&image_data[0]); + if (!thumbnail.is_valid()) + return; + GLint last_texture; + GLuint id = (GLuint)(intptr_t)texture_id; glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); - glsafe(::glGenTextures(1, &m_image_texture)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_image_texture)); + if (id == 0) + glsafe(::glGenTextures(1, &id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, id)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); - - // Store our identifier - texture_id = (ImTextureID)(intptr_t)m_image_texture; - - // Restore state + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, thumbnail.width, thumbnail.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, thumbnail.pixels.data())); + texture_id = (ImTextureID)(intptr_t)id; glsafe(::glBindTexture(GL_TEXTURE_2D, last_texture)); - - return true; } void IMToolbar::del_all_item() diff --git a/src/slic3r/GUI/IMToolbar.hpp b/src/slic3r/GUI/IMToolbar.hpp index ce8770d27f..54d7fabf61 100644 --- a/src/slic3r/GUI/IMToolbar.hpp +++ b/src/slic3r/GUI/IMToolbar.hpp @@ -12,6 +12,8 @@ #define DEFAULT_TOOLBAR_BUTTON_HEIGHT 80.0f namespace Slic3r { +struct ThumbnailData; + namespace GUI { class IMToolbarItem @@ -33,11 +35,9 @@ public: SliceState slice_state; ImTextureID texture_id { 0 }; - std::vector image_data; - unsigned int image_width; - unsigned int image_height; - bool generate_texture(); + // Uploads a valid thumbnail as the texture; the item keeps no copy of its pixels. + void set_thumbnail(const ThumbnailData& thumbnail); ~IMToolbarItem(); }; diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 35d0da7525..ae0c0c46db 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -6155,6 +6155,14 @@ int PartPlateList::select_plate(int index) //wxQueueEvent(m_plater, new SimpleEvent(EVT_GLCANVAS_PLATE_SELECT)); } + // With the other plates hidden, the view follows the selected plate. + if (previous_index != index && m_intialized && m_plater != nullptr && m_plater->is_view3D_shown() && wxGetApp().focus_mode()) { + Camera& camera = m_plater->get_camera(); + Vec3d target = camera.get_target(); + target.head<2>() = m_plate_list[index]->get_bounding_box().center().head<2>(); + camera.set_target(target); + } + if (previous_index != index && m_intialized && m_plater != nullptr && !m_plater->is_loading_project()) { LifecycleEventContext ctx; ctx.name = m_plate_list[index]->get_plate_name(); @@ -7064,7 +7072,7 @@ void PartPlateList::postprocess_arrange_polygon(arrangement::ArrangePolygon& arr /*rendering related functions*/ //render -void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid, bool hide_chrome) +void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid, bool hide_chrome, bool hide_inactive) { const std::lock_guard local_lock(m_plates_mutex); std::vector::iterator it = m_plate_list.begin(); @@ -7079,7 +7087,7 @@ void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& pr load_icon_textures(); for (it = m_plate_list.begin(); it != m_plate_list.end(); it++) { int current_index = (*it)->get_index(); - if (only_current && (current_index != m_current_plate)) + if ((only_current || hide_inactive) && (current_index != m_current_plate)) continue; if (current_index == m_current_plate) { PartPlate::HeightLimitMode height_mode = (only_current)?PartPlate::HEIGHT_LIMIT_NONE:m_height_limit_mode; diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index ba6fb74a29..ac7a9db0b6 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -1008,7 +1008,7 @@ public: /*rendering related functions*/ void on_change_color_mode(bool is_dark) { m_is_dark = is_dark; } - void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true, bool hide_chrome = false); + void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true, bool hide_chrome = false, bool hide_inactive = false); void set_render_option(bool bedtype_texture, bool plate_settings); void set_render_cali(bool value = true) { render_cali_logo = value; } void render_hover_tooltip() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c91ba60808..6e4d3e34e0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -436,6 +436,7 @@ void Plater::show_illegal_characters_warning(wxWindow* parent) void Plater::mark_plate_toolbar_image_dirty() { m_b_plate_toolbar_image_dirty = true; + ++m_plate_toolbar_image_version; } bool Plater::is_plate_toolbar_image_dirty() const @@ -18268,16 +18269,20 @@ wxString Plater::get_project_name() void Plater::update_all_plate_thumbnails(bool force_update) { - for (int i = 0; i < get_partplate_list().get_plate_count(); i++) { - PartPlate* plate = get_partplate_list().get_plate(i); - ThumbnailsParams thumbnail_params = { {}, false, true, true, true, i}; - if (force_update || !plate->thumbnail_data.is_valid()) { - get_view3D_canvas3D()->render_thumbnail(plate->thumbnail_data, plate->plate_thumbnail_width, plate->plate_thumbnail_height, thumbnail_params, Camera::EType::Ortho); - } - if (force_update || !plate->no_light_thumbnail_data.is_valid()) { - get_view3D_canvas3D()->render_thumbnail(plate->no_light_thumbnail_data, plate->plate_thumbnail_width, plate->plate_thumbnail_height, thumbnail_params, - Camera::EType::Ortho, Camera::ViewAngleType::Iso, false, true); - } + for (int i = 0; i < get_partplate_list().get_plate_count(); i++) + update_plate_thumbnails(i, force_update); +} + +void Plater::update_plate_thumbnails(int plate_idx, bool force_update) +{ + PartPlate* plate = get_partplate_list().get_plate(plate_idx); + ThumbnailsParams thumbnail_params = { {}, false, true, true, true, plate_idx}; + if (force_update || !plate->thumbnail_data.is_valid()) { + get_view3D_canvas3D()->render_thumbnail(plate->thumbnail_data, plate->plate_thumbnail_width, plate->plate_thumbnail_height, thumbnail_params, Camera::EType::Ortho); + } + if (force_update || !plate->no_light_thumbnail_data.is_valid()) { + get_view3D_canvas3D()->render_thumbnail(plate->no_light_thumbnail_data, plate->plate_thumbnail_width, plate->plate_thumbnail_height, thumbnail_params, + Camera::EType::Ortho, Camera::ViewAngleType::Iso, false, true); } } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 0bb2d65add..564addb0c7 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -434,6 +434,7 @@ public: bool is_new_project_and_check_state() { return m_new_project_and_check_state; } wxString get_project_name(); void update_all_plate_thumbnails(bool force_update = false); + void update_plate_thumbnails(int plate_idx, bool force_update); void update_obj_preview_thumbnail(ModelObject *, int obj_idx, int vol_idx, std::vector colors, int camera_view_angle_type); void invalid_all_plate_thumbnails(); void force_update_all_plate_thumbnails(); @@ -1071,6 +1072,8 @@ public: void mark_plate_toolbar_image_dirty(); bool is_plate_toolbar_image_dirty() const; void clear_plate_toolbar_image_dirty(); + // Bumped each time the plate thumbnails go stale, so each consumer can track it on its own. + unsigned plate_toolbar_image_version() const { return m_plate_toolbar_image_version; } private: struct priv; @@ -1095,6 +1098,7 @@ private: int m_valid_plates_count { 0 }; int m_check_status = 0; // 0 not check, 1 check success, 2 check failed bool m_b_plate_toolbar_image_dirty{ true }; + unsigned m_plate_toolbar_image_version{ 0 }; void suppress_snapshots(); void allow_snapshots();