diff --git a/resources/images/plate_hidden.svg b/resources/images/plate_hidden.svg new file mode 100644 index 0000000000..d805f48d87 --- /dev/null +++ b/resources/images/plate_hidden.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/images/plate_hidden_dark.svg b/resources/images/plate_hidden_dark.svg new file mode 100644 index 0000000000..6e6a5c82dd --- /dev/null +++ b/resources/images/plate_hidden_dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/images/plate_hidden_hover.svg b/resources/images/plate_hidden_hover.svg new file mode 100644 index 0000000000..8ff60883bb --- /dev/null +++ b/resources/images/plate_hidden_hover.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/images/plate_hidden_hover_dark.svg b/resources/images/plate_hidden_hover_dark.svg new file mode 100644 index 0000000000..9d9c7abc86 --- /dev/null +++ b/resources/images/plate_hidden_hover_dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/images/plate_visible.svg b/resources/images/plate_visible.svg new file mode 100644 index 0000000000..40ab3f7e80 --- /dev/null +++ b/resources/images/plate_visible.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/images/plate_visible_dark.svg b/resources/images/plate_visible_dark.svg new file mode 100644 index 0000000000..651fd4527f --- /dev/null +++ b/resources/images/plate_visible_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/images/plate_visible_hover.svg b/resources/images/plate_visible_hover.svg new file mode 100644 index 0000000000..eb3a470d94 --- /dev/null +++ b/resources/images/plate_visible_hover.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/images/plate_visible_hover_dark.svg b/resources/images/plate_visible_hover_dark.svg new file mode 100644 index 0000000000..245e39562f --- /dev/null +++ b/resources/images/plate_visible_hover_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/images/toolbar_plate_visibility.svg b/resources/images/toolbar_plate_visibility.svg new file mode 100644 index 0000000000..3284120777 --- /dev/null +++ b/resources/images/toolbar_plate_visibility.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/images/toolbar_plate_visibility_dark.svg b/resources/images/toolbar_plate_visibility_dark.svg new file mode 100644 index 0000000000..d176408d5c --- /dev/null +++ b/resources/images/toolbar_plate_visibility_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 96223296ef..080f5ee658 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2034,6 +2034,39 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject _set_warning_notification(EWarning::SomethingNotShown, false); } +void GLCanvas3D::update_plate_volumes_visibility(PartPlateList& plate_list) +{ + // Build a set of (object_id, instance_id) pairs that belong to hidden plates + std::set> hidden_instances; + for (int i = 0; i < plate_list.get_plate_count(); i++) { + const PartPlate* plate = plate_list.get_plate(i); + if (plate && !plate->is_visible()) { + for (const auto& pair : plate->get_instance_set()) + hidden_instances.insert(pair); + } + } + + for (GLVolume* vol : m_volumes.volumes) { + int obj_id = vol->composite_id.object_id; + int inst_id = vol->composite_id.instance_id; + bool in_hidden; + if (vol->is_wipe_tower) { + int wt_plate_id = obj_id - 1000; + const PartPlate* wt_plate = plate_list.get_plate(wt_plate_id); + in_hidden = wt_plate && !wt_plate->is_visible(); + } else if (obj_id < 0 || obj_id >= 1000) { + continue; + } else { + in_hidden = hidden_instances.count({obj_id, inst_id}) > 0; + } + if (in_hidden) + vol->is_active = false; + else if (!vol->is_active) { + vol->is_active = true; + } + } +} + void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) { ModelObject* model_object = m_model->objects[obj_idx]; @@ -3744,6 +3777,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re #endif } wxGetApp().plater()->get_partplate_list().reset_thumbnail_assembly_view_data(); // reload scene + // Re-apply per-plate visibility (newly loaded wipe towers default to active). + update_plate_volumes_visibility(wxGetApp().plater()->get_partplate_list()); // and force this canvas to be redrawn. m_dirty = true; } @@ -7410,6 +7445,94 @@ bool GLCanvas3D::_init_main_toolbar() if (!p_main_toolbar->add_item(item)) return false; } + + { + GLToolbarItem::Data item; + item.name = "plate_visibility"; + item.icon_filename_callback = [](bool is_dark_mode)->std::string { + return is_dark_mode ? "toolbar_plate_visibility_dark.svg" : "toolbar_plate_visibility.svg"; + }; + item.tooltip = _utf8(L("Show/Hide Plates")); + item.sprite_id = sprite_id++; + item.left.toggable = false; + item.left.action_callback = [this]() { + if (m_canvas == nullptr) return; + PartPlateList& list = wxGetApp().plater()->get_partplate_list(); + if (list.get_plate_count() <= 1) return; + wxMenu menu; + + auto do_refresh = [this, &list]() { + list.update_unselected_plate_trans(list.get_plate_count()); + update_plate_volumes_visibility(list); + wxGetApp().obj_list()->reload_all_plates(); + wxGetApp().plater()->update(); + }; + + // Show All + wxMenuItem* show_all = menu.Append(wxID_ANY, _L("Show All Plates")); + menu.Bind(wxEVT_MENU, [this, &list, do_refresh](wxCommandEvent&) { + for (int i = 0; i < list.get_plate_count(); i++) { + PartPlate* p = list.get_plate(i); + if (p && !p->is_visible()) p->set_visible(true); + } + do_refresh(); + }, show_all->GetId()); + + // Hide Other Plates — disabled if current plate is hidden or no others are visible + int curr_idx = list.get_curr_plate_index(); + PartPlate* curr_plate = list.get_plate(curr_idx); + bool curr_is_visible = curr_plate && curr_plate->is_visible(); + wxMenuItem* hide_others = menu.Append(wxID_ANY, _L("Hide Other Plates")); + bool any_other_visible = false; + for (int i = 0; i < list.get_plate_count(); i++) + if (i != curr_idx && list.get_plate(i) && list.get_plate(i)->is_visible()) + any_other_visible = true; + if (!any_other_visible || !curr_is_visible) hide_others->Enable(false); + menu.Bind(wxEVT_MENU, [this, &list, curr_idx, do_refresh](wxCommandEvent&) { + PartPlate* curr = list.get_plate(curr_idx); + if (!curr || !curr->is_visible()) return; + for (int i = 0; i < list.get_plate_count(); i++) { + PartPlate* p = list.get_plate(i); + if (p && i != curr_idx && p->is_visible()) p->set_visible(false); + } + do_refresh(); + }, hide_others->GetId()); + + menu.AppendSeparator(); + + // Per-plate toggles + for (int i = 0; i < list.get_plate_count(); i++) { + PartPlate* plate = list.get_plate(i); + if (!plate) continue; + wxString name = from_u8(plate->get_plate_name()); + if (name.empty()) + name = wxString::Format(_L("Plate %d"), i + 1); + else + name = wxString::Format("%s (%d)", name, i + 1); + wxMenuItem* mi = menu.AppendCheckItem(wxID_ANY, name); + mi->Check(plate->is_visible()); + if (plate->is_visible() && list.get_visible_plate_count() <= 1) + mi->Enable(false); + menu.Bind(wxEVT_MENU, [this, i, &list, do_refresh](wxCommandEvent&) { + PartPlate* p = list.get_plate(i); + if (!p) return; + if (p->is_visible() && list.get_visible_plate_count() <= 1) return; + p->set_visible(!p->is_visible()); + do_refresh(); + }, mi->GetId()); + } + m_canvas->PopupMenu(&menu); + }; + item.left.render_callback = GLToolbarItem::Default_Render_Callback; + item.visible = true; + item.visibility_callback = [this]()->bool { + return wxGetApp().plater()->get_partplate_list().get_plate_count() > 1; + }; + item.enabling_callback = []()->bool { return true; }; + item.b_collapsible = false; + if (!p_main_toolbar->add_item(item)) + return false; + } } p_main_toolbar->update_items_state(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index e199a8db07..082df7d997 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -860,6 +860,7 @@ class GLCanvas3D void toggle_selected_volume_visibility(bool selected_visible); void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1, const ModelVolume* mv = nullptr); + void update_plate_volumes_visibility(PartPlateList& plate_list); void update_instance_printable_state_for_object(size_t obj_idx); void update_instance_printable_state_for_objects(const std::vector& object_idxs); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 961968d408..0365aef97f 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1844,6 +1844,7 @@ wxMenu *MenuFactory::filament_action_menu(int active_filament_menu_id) { wxMenu* MenuFactory::plate_menu() { append_menu_item_locked(&m_plate_menu); + append_menu_item_plate_visibility(&m_plate_menu); append_menu_item_plate_name(&m_plate_menu); { NetworkAgent* agent = GUI::wxGetApp().getAgent(); @@ -2433,6 +2434,78 @@ void MenuFactory::append_menu_item_locked(wxMenu* menu) }, item->GetId()); } +void MenuFactory::append_menu_item_plate_visibility(wxMenu* menu) +{ + const std::vector names = { + _L("Show Plate"), _L("Hide Plate"), + _L("Show All Plates"), _L("Hide Other Plates") + }; + for (const wxString& name : names) { + int item_id; + while ((item_id = menu->FindItem(name)) != wxNOT_FOUND) + menu->Destroy(item_id); + } + + PartPlateList& list = plater()->get_partplate_list(); + if (list.get_plate_count() <= 1) + return; + + PartPlate* plate = list.get_selected_plate(); + if (!plate) return; + + auto do_refresh = []() { + PartPlateList& list = plater()->get_partplate_list(); + list.update_unselected_plate_trans(list.get_plate_count()); + plater()->get_view3D_canvas3D()->update_plate_volumes_visibility(list); + wxGetApp().obj_list()->reload_all_plates(); + plater()->update(); + }; + + // Show All + auto show_all = append_menu_item(menu, wxID_ANY, _L("Show All Plates"), "", + [do_refresh](wxCommandEvent&) { + PartPlateList& list = plater()->get_partplate_list(); + for (int i = 0; i < list.get_plate_count(); i++) { + PartPlate* p = list.get_plate(i); + if (p && !p->is_visible()) p->set_visible(true); + } + do_refresh(); + }, "", nullptr, nullptr, nullptr); + + // Hide Other Plates — disabled if current plate is hidden (would leave 0 visible) or no others are visible + int curr_idx = list.get_curr_plate_index(); + bool curr_is_visible = plate->is_visible(); + bool any_other_visible = false; + for (int i = 0; i < list.get_plate_count(); i++) + if (i != curr_idx && list.get_plate(i) && list.get_plate(i)->is_visible()) + any_other_visible = true; + auto hide_others = append_menu_item(menu, wxID_ANY, _L("Hide Other Plates"), "", + [do_refresh, curr_idx](wxCommandEvent&) { + PartPlateList& list = plater()->get_partplate_list(); + PartPlate* curr = list.get_plate(curr_idx); + if (!curr || !curr->is_visible()) return; + for (int i = 0; i < list.get_plate_count(); i++) { + PartPlate* p = list.get_plate(i); + if (p && i != curr_idx && p->is_visible()) p->set_visible(false); + } + do_refresh(); + }, "", nullptr, nullptr, nullptr); + if (!any_other_visible || !curr_is_visible) hide_others->Enable(false); + + // Single plate toggle (only if not the last visible) + bool is_last_visible = plate->is_visible() && list.get_visible_plate_count() <= 1; + if (!is_last_visible) { + wxString vis_text = plate->is_visible() ? names[1] : names[0]; + append_menu_item(menu, wxID_ANY, vis_text, "", + [plate, do_refresh](wxCommandEvent&) { + PartPlateList& list = plater()->get_partplate_list(); + if (plate->is_visible() && list.get_visible_plate_count() <= 1) return; + plate->set_visible(!plate->is_visible()); + do_refresh(); + }, "", nullptr, nullptr, nullptr); + } +} + void MenuFactory::append_menu_item_fill_bed(wxMenu *menu) { append_menu_item( diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 73a45e366c..36ef53a717 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -179,6 +179,7 @@ class MenuFactory void append_menu_item_change_filament(wxMenu* menu); void append_menu_item_set_printable(wxMenu* menu); void append_menu_item_locked(wxMenu* menu); + void append_menu_item_plate_visibility(wxMenu* menu); void append_menu_item_fill_bed(wxMenu *menu); void append_menu_item_plate_name(wxMenu *menu); void append_menu_item_align_distribute(wxMenu *menu); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b2a3260d33..12b07eef29 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -3111,6 +3111,92 @@ void MainFrame::init_menubar_as_editor() m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->is_view3D_overhang_shown(); }, this); + + // "Show/Hide Plates" submenu — rebuilt dynamically on open + wxMenu* plate_visibility_submenu = new wxMenu(); + wxMenuItem* plate_vis_submenu_item = viewMenu->AppendSubMenu(plate_visibility_submenu, _L("Show/Hide Plates")); + + // Rebuild the per-plate checkable items each time the parent View menu is opened + viewMenu->Bind(wxEVT_MENU_OPEN, [this, plate_visibility_submenu](wxMenuEvent&) { + // Clear and rebuild + while (plate_visibility_submenu->GetMenuItemCount() > 0) + plate_visibility_submenu->Destroy(plate_visibility_submenu->FindItemByPosition(0)); + + PartPlateList& list = m_plater->get_partplate_list(); + int count = list.get_plate_count(); + if (count <= 1) return; + + auto do_refresh = [this, &list]() { + list.update_unselected_plate_trans(list.get_plate_count()); + m_plater->get_view3D_canvas3D()->update_plate_volumes_visibility(list); + wxGetApp().obj_list()->reload_all_plates(); + m_plater->update(); + }; + + // Show All + wxMenuItem* show_all = plate_visibility_submenu->Append(wxID_ANY, _L("Show All Plates")); + plate_visibility_submenu->Bind(wxEVT_MENU, [this, do_refresh](wxCommandEvent&) { + PartPlateList& list = m_plater->get_partplate_list(); + for (int i = 0; i < list.get_plate_count(); i++) { + PartPlate* p = list.get_plate(i); + if (p && !p->is_visible()) p->set_visible(true); + } + do_refresh(); + }, show_all->GetId()); + + // Hide Other Plates — disabled if current plate is hidden or no others are visible + int curr_idx = list.get_curr_plate_index(); + PartPlate* curr_plate = list.get_plate(curr_idx); + bool curr_is_visible = curr_plate && curr_plate->is_visible(); + wxMenuItem* hide_others = plate_visibility_submenu->Append(wxID_ANY, _L("Hide Other Plates")); + bool any_other_visible = false; + for (int i = 0; i < count; i++) + if (i != curr_idx && list.get_plate(i) && list.get_plate(i)->is_visible()) + any_other_visible = true; + if (!any_other_visible || !curr_is_visible) hide_others->Enable(false); + plate_visibility_submenu->Bind(wxEVT_MENU, [this, curr_idx, do_refresh](wxCommandEvent&) { + PartPlateList& list = m_plater->get_partplate_list(); + PartPlate* curr = list.get_plate(curr_idx); + if (!curr || !curr->is_visible()) return; + for (int i = 0; i < list.get_plate_count(); i++) { + PartPlate* p = list.get_plate(i); + if (p && i != curr_idx && p->is_visible()) p->set_visible(false); + } + do_refresh(); + }, hide_others->GetId()); + + plate_visibility_submenu->AppendSeparator(); + + for (int i = 0; i < count; i++) { + PartPlate* plate = list.get_plate(i); + if (!plate) continue; + wxString plate_name = from_u8(plate->get_plate_name()); + if (plate_name.empty()) + plate_name = wxString::Format(_L("Plate %d"), i + 1); + else + plate_name = wxString::Format("%s (%d)", plate_name, i + 1); + + wxMenuItem* item = plate_visibility_submenu->AppendCheckItem(wxID_ANY, plate_name); + item->Check(plate->is_visible()); + // Disable if this is the last visible plate (can't hide it) + if (plate->is_visible() && list.get_visible_plate_count() <= 1) + item->Enable(false); + + plate_visibility_submenu->Bind(wxEVT_MENU, [this, i](wxCommandEvent&) { + PartPlateList& list = m_plater->get_partplate_list(); + if (list.get_plate_count() <= 1) return; + PartPlate* p = list.get_plate(i); + if (!p) return; + // Guard: don't allow hiding the last visible plate + if (p->is_visible() && list.get_visible_plate_count() <= 1) return; + p->set_visible(!p->is_visible()); + list.update_unselected_plate_trans(list.get_plate_count()); + m_plater->get_view3D_canvas3D()->update_plate_volumes_visibility(list); + wxGetApp().obj_list()->reload_all_plates(); + m_plater->update(); + }, item->GetId()); + } + }); viewMenu->AppendSeparator(); append_menu_item( viewMenu, wxID_ANY, _L("Set 3DConnexion"), _L("Set 3DConnexion mouse"), diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 32c0ee567f..2336417f0d 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -512,6 +512,9 @@ wxDataViewItem ObjectDataViewModel::AddPlate(PartPlate* part_plate, wxString nam if (!part_plate->get_plate_name().empty()) { plate_name += wxString(" (", wxConvUTF8) + from_u8(part_plate->get_plate_name()) + wxString(")", wxConvUTF8); } + if (part_plate && !part_plate->is_visible()) { + plate_name += wxString(" [") + _L("hidden") + wxString("]"); + } } auto plate_node = new ObjectDataViewModelNode(part_plate, plate_name); @@ -1322,6 +1325,7 @@ wxDataViewItem ObjectDataViewModel::GetItemByPlateId(int plate_idx) } void ObjectDataViewModel::SetCurSelectedPlateFullName(int plate_idx, const std::string & custom_name) { + PartPlateList* ppl = wxGetApp().plater() ? &wxGetApp().plater()->get_partplate_list() : nullptr; for (auto plate : m_plates) { if (plate->m_plate_idx == plate_idx) { wxString plate_full_name =_L("Plate"); @@ -1329,6 +1333,11 @@ void ObjectDataViewModel::SetCurSelectedPlateFullName(int plate_idx, const std:: if (!custom_name.empty()) { plate_full_name += wxString(" (", wxConvUTF8) + from_u8(custom_name) + wxString(")", wxConvUTF8); } + if (ppl) { + PartPlate* pp = ppl->get_plate(plate_idx); + if (pp && !pp->is_visible()) + plate_full_name += wxString(" [") + _L("hidden") + wxString("]"); + } plate->SetName(plate_full_name); } } diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index f1d6c26b9a..b5b3604c50 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -116,7 +116,7 @@ PartPlate::PartPlate() } PartPlate::PartPlate(PartPlateList *partplate_list, Vec3d origin, int width, int depth, int height, Plater* platerObj, Model* modelObj, bool printable, PrinterTechnology tech) - :m_partplate_list(partplate_list), m_plater(platerObj), m_model(modelObj), printer_technology(tech), m_origin(origin), m_width(width), m_depth(depth), m_height(height), m_printable(printable) + :m_partplate_list(partplate_list), m_plater(platerObj), m_model(modelObj), printer_technology(tech), m_origin(origin), m_width(width), m_depth(depth), m_height(height), m_printable(printable), m_visible(true) { init(); } @@ -785,6 +785,27 @@ void PartPlate::render_icons(bool bottom, bool only_body, int hover_id, bool ren render_icon_texture(m_partplate_list->m_lock_icon, m_partplate_list->m_lockopen_texture); } + // visibility (eye) icon — slot PLATE_VISIBLE_ID + // Show only when hiding is possible: plate count > 1 and at least one other plate is visible. + bool can_toggle_visibility = m_partplate_list->get_plate_count() > 1 && + (!this->is_visible() || m_partplate_list->get_visible_plate_count() > 1); + if (can_toggle_visibility) { + if (hover_id == PLATE_VISIBLE_ID) { + if (this->is_visible()) { + render_icon_texture(m_partplate_list->m_plate_visible_icon, m_partplate_list->m_plate_visible_hovered_texture); + show_tooltip(_u8L("Hide current plate")); + } else { + render_icon_texture(m_partplate_list->m_plate_visible_icon, m_partplate_list->m_plate_hidden_hovered_texture); + show_tooltip(_u8L("Show current plate")); + } + } else { + if (this->is_visible()) + render_icon_texture(m_partplate_list->m_plate_visible_icon, m_partplate_list->m_plate_visible_texture); + else + render_icon_texture(m_partplate_list->m_plate_visible_icon, m_partplate_list->m_plate_hidden_texture); + } + } + int extruder_count = wxGetApp().preset_bundle->get_printer_extruder_count(); if (extruder_count == 2) { if (hover_id == PLATE_FILAMENT_MAP_ID){ @@ -1085,11 +1106,23 @@ void PartPlate::on_render_for_picking() { } shader->set_uniform("projection_matrix", proj_mat); + // Order MUST match GRABBER_COUNT slot indices used in select_plate_by_hover_id(): + // 0=select, 1=delete, 2=orient, 3=arrange, 4=lock, 5=settings, + // 6=visible(PLATE_VISIBLE_ID), 7=filament_map(PLATE_FILAMENT_MAP_ID), 8=name(PLATE_NAME_ID) std::vector gl_models = {&m_partplate_list->m_triangles, &m_partplate_list->m_del_icon, &m_partplate_list->m_orient_icon, &m_partplate_list->m_arrange_icon, &m_partplate_list->m_lock_icon, &m_partplate_list->m_plate_settings_icon, + &m_partplate_list->m_plate_visible_icon, &m_partplate_list->m_plate_filament_map_icon,//some case not show &m_plate_name_edit_icon}; + + // Hidden plates only expose the eye icon for picking so the user can restore them. + // All other slots (including the plate body at index 0) are skipped so the plate + // is not accidentally selected or acted upon. + bool hidden = !m_visible; + for (size_t i = 0; i < gl_models.size(); i++) { + if (hidden && i != PLATE_VISIBLE_ID) + continue; if (!gl_models[i]->get_visible()) { continue; } @@ -3965,15 +3998,17 @@ void PartPlateList::update_unselected_plate_trans(int count) { return; } m_update_unselected_plate_mats_vbo = true; - m_unselected_plate_trans.resize(count - 1); int cols = compute_colum_count(count); - int index = 0; + m_unselected_plate_trans.clear(); for (size_t i = 0; i < count; i++) { - if (i == m_current_plate) { continue; } + if ((int)i == m_current_plate) { continue; } + // skip hidden plates — they are not rendered in the viewport + if (i < m_plate_list.size() && !m_plate_list[i]->is_visible()) { continue; } Vec2d pos = compute_shape_position(i, cols); Vec3d plate_origin = Vec3d(pos.x(), pos.y(), 0); - m_unselected_plate_trans[index].set_offset(plate_origin); - index++; + Geometry::Transformation t; + t.set_offset(plate_origin); + m_unselected_plate_trans.push_back(t); } } @@ -4372,6 +4407,34 @@ void PartPlateList::generate_icon_textures() } } + { + file_name = path + (m_is_dark ? "plate_visible_dark.svg" : "plate_visible.svg"); + if (!m_plate_visible_texture.load_from_svg_file(file_name, true, false, false, icon_size)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":load file %1% failed") % file_name; + } + } + + { + file_name = path + (m_is_dark ? "plate_visible_hover_dark.svg" : "plate_visible_hover.svg"); + if (!m_plate_visible_hovered_texture.load_from_svg_file(file_name, true, false, false, icon_size)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":load file %1% failed") % file_name; + } + } + + { + file_name = path + (m_is_dark ? "plate_hidden_dark.svg" : "plate_hidden.svg"); + if (!m_plate_hidden_texture.load_from_svg_file(file_name, true, false, false, icon_size)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":load file %1% failed") % file_name; + } + } + + { + file_name = path + (m_is_dark ? "plate_hidden_hover_dark.svg" : "plate_hidden_hover.svg"); + if (!m_plate_hidden_hovered_texture.load_from_svg_file(file_name, true, false, false, icon_size)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":load file %1% failed") % file_name; + } + } + //if (m_bedtype_changed_texture.get_id() == 0) { file_name = path + (m_is_dark ? "plate_settings_changed_dark.svg" : "plate_settings_changed.svg"); @@ -4470,6 +4533,10 @@ void PartPlateList::release_icon_textures() m_plate_settings_hovered_texture.reset(); m_plate_set_filament_map_texture.reset(); m_plate_set_filament_map_hovered_texture.reset(); + m_plate_visible_texture.reset(); + m_plate_visible_hovered_texture.reset(); + m_plate_hidden_texture.reset(); + m_plate_hidden_hovered_texture.reset(); m_plate_name_edit_texture.reset(); m_plate_name_edit_hovered_texture.reset(); for (int i = 0;i < MAX_PLATE_COUNT; i++) { @@ -5146,6 +5213,14 @@ int PartPlateList::get_plate_count() const return ret; } +int PartPlateList::get_visible_plate_count() const +{ + int count = 0; + for (const PartPlate* p : m_plate_list) + if (p && p->is_visible()) ++count; + return count; +} + //update the plate cols due to plate count change void PartPlateList::update_plate_cols() { @@ -6003,11 +6078,12 @@ void PartPlateList::render_instance(bool bottom, bool only_current, bool only_bo wxGetApp().unbind_shader(); } const auto& shader = wxGetApp().get_shader("flat"); + bool curr_plate_visible = (m_current_plate < (int)m_plate_list.size()) ? m_plate_list[m_current_plate]->is_visible() : true; {//for selected wxGetApp().bind_shader(shader); shader->set_uniform("view_model_matrix", view_mat * m_plate_trans[m_current_plate].get_matrix()); shader->set_uniform("projection_matrix", proj_mat); - if (!bottom) { // draw background + if (!bottom && curr_plate_visible) { // draw background (skip if plate is hidden) render_exclude_area(force_background_color); // for selected_plate if(wxGetApp().plater()->get_enable_wrapping_detection()){ if(!m_wrapping_detection_triangles.is_initialized()){ @@ -6027,8 +6103,8 @@ void PartPlateList::render_instance(bool bottom, bool only_current, bool only_bo render_wrapping_detection_area(force_background_color); } } - if (show_grid) - render_grid(bottom); // for selected_plate + if (show_grid && curr_plate_visible) + render_grid(bottom); // for selected_plate (skip if hidden) } if (enable_multi_instance) { wxGetApp().unbind_shader(); @@ -6222,6 +6298,13 @@ void PartPlateList::render(bool bottom, bool only_current, bool only_body, int h int current_index = (*it)->get_index(); if (only_current && (current_index != m_current_plate)) continue; + // Hidden plates: only render their icons so the user can click the eye to restore them. + // The plate background, grid, and logos are skipped entirely. + if (!(*it)->is_visible()) { + int action = (m_plate_hover_index == current_index) ? m_plate_hover_action : -1; + (*it)->render_icons(bottom, only_body, action); + continue; + } if (current_index == m_current_plate) { PartPlate::HeightLimitMode height_mode = (only_current)?PartPlate::HEIGHT_LIMIT_NONE:m_height_limit_mode; if (m_plate_hover_index == current_index) @@ -6381,6 +6464,7 @@ bool PartPlateList::set_shapes(const Pointfs &shape, calc_vertex_for_icons(3, m_lock_icon); calc_vertex_for_icons(4, m_plate_settings_icon); calc_vertex_for_icons(5, m_plate_filament_map_icon); + calc_vertex_for_icons(6, m_plate_visible_icon); calc_vertex_for_number(0, false, m_plate_idx_icon); } return true; @@ -6470,6 +6554,7 @@ bool PartPlateList::is_all_slice_results_ready_for_print() const bool res = false; for (unsigned int i = 0; i < (unsigned int) m_plate_list.size(); ++i) { + if (!m_plate_list[i]->is_visible()) continue; if (!m_plate_list[i]->empty()) { if (m_plate_list[i]->is_all_instances_unprintable()) { continue; diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 22015c8906..2ad7ee5fa7 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -102,6 +102,7 @@ class PartPlate : public ObjectBase float m_height_to_rod; bool m_printable; bool m_locked; + bool m_visible {true}; bool m_ready_for_slice; bool m_slice_result_valid; bool m_apply_invalid {false}; @@ -191,9 +192,10 @@ class PartPlate : public ObjectBase public: static const unsigned int PLATE_BASE_ID = 255 * 255 * 253; - static const unsigned int GRABBER_COUNT = 8; + static const unsigned int GRABBER_COUNT = 9; + static const unsigned int PLATE_VISIBLE_ID = GRABBER_COUNT - 3; static const unsigned int PLATE_FILAMENT_MAP_ID = GRABBER_COUNT - 2; - static const unsigned int PLATE_NAME_ID = GRABBER_COUNT-1; + static const unsigned int PLATE_NAME_ID = GRABBER_COUNT - 1; static std::array SELECT_COLOR; static std::array UNSELECT_COLOR; @@ -428,13 +430,22 @@ class PartPlate : public ObjectBase bool is_locked() const { return m_locked; } void lock(bool state) { m_locked = state; } + //is visible in the 3D viewport or not + bool is_visible() const { return m_visible; } + void set_visible(bool state) { + m_visible = state; + if (!state) update_slice_result_valid_state(false); + } + + const std::set>& get_instance_set() const { return obj_to_instance_set; } + //is a printable plate or not bool is_printable() const { return m_printable; } //can be sliced or not bool can_slice() const { - return m_ready_for_slice && !m_apply_invalid; + return m_visible && m_ready_for_slice && !m_apply_invalid; } bool can_helio_slice() const { @@ -477,12 +488,13 @@ class PartPlate : public ObjectBase //is slice result valid or not bool is_slice_result_valid() const { - return m_slice_result_valid; + return m_visible && m_slice_result_valid; } //is slice result ready for print bool is_slice_result_ready_for_print() const { + if (!m_visible) return false; bool result = m_slice_result_valid; if (result) result = m_gcode_result ? @@ -494,7 +506,7 @@ class PartPlate : public ObjectBase // check whether plate's slice result valid for export to file bool is_slice_result_ready_for_export() { - return is_slice_result_ready_for_print() && has_printable_instances(); + return m_visible && is_slice_result_ready_for_print() && has_printable_instances(); } //invalid sliced result @@ -555,7 +567,7 @@ class PartPlate : public ObjectBase std::vector> objects_and_instances; std::vector> instances_outside; - ar(m_plate_index, m_print_index, m_locked, m_selected, m_ready_for_slice, m_slice_result_valid, m_apply_invalid, m_printable, m_tmp_gcode_path, objects_and_instances, instances_outside, m_config, m_name); + ar(m_plate_index, m_print_index, m_locked, m_selected, m_ready_for_slice, m_slice_result_valid, m_apply_invalid, m_printable, m_visible, m_tmp_gcode_path, objects_and_instances, instances_outside, m_config, m_name); for (std::vector>::iterator it = objects_and_instances.begin(); it != objects_and_instances.end(); ++it) obj_to_instance_set.insert(std::pair(it->first, it->second)); @@ -573,7 +585,7 @@ class PartPlate : public ObjectBase for (std::set>::iterator it = obj_to_instance_set.begin(); it != obj_to_instance_set.end(); ++it) objects_and_instances.emplace_back(it->first, it->second); - ar(m_plate_index, m_print_index, m_locked, m_selected, m_ready_for_slice, m_slice_result_valid, m_apply_invalid, m_printable,m_tmp_gcode_path, objects_and_instances, instances_outside, m_config, m_name); + ar(m_plate_index, m_print_index, m_locked, m_selected, m_ready_for_slice, m_slice_result_valid, m_apply_invalid, m_printable, m_visible, m_tmp_gcode_path, objects_and_instances, instances_outside, m_config, m_name); } /*template void serialize(Archive& ar) { @@ -634,6 +646,10 @@ class PartPlateList : public ObjectBase GLTexture m_plate_settings_changed_hovered_texture; GLTexture m_plate_set_filament_map_texture; GLTexture m_plate_set_filament_map_hovered_texture; + GLTexture m_plate_visible_texture; + GLTexture m_plate_visible_hovered_texture; + GLTexture m_plate_hidden_texture; + GLTexture m_plate_hidden_hovered_texture; GLTexture m_plate_name_edit_texture; GLTexture m_plate_name_edit_hovered_texture; GLTexture m_idx_textures[MAX_PLATE_COUNT]; @@ -694,6 +710,7 @@ class PartPlateList : public ObjectBase GLModel m_lock_icon; GLModel m_plate_settings_icon; GLModel m_plate_filament_map_icon; + GLModel m_plate_visible_icon; GLModel m_plate_idx_icon; float m_scale_factor{1.0f}; @@ -847,6 +864,7 @@ class PartPlateList : public ObjectBase //get the plate counts, not including the invalid plate int get_plate_count() const; + int get_visible_plate_count() const; //update the plate cols due to plate count change void update_plate_cols(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c9f7ee461f..9b28020a4b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -9324,7 +9324,7 @@ void Plater::priv::object_list_changed() //sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits && part_plate->has_printable_instances()); bool mixed_broken = sidebar->has_broken_mixed_filament(); bool can_slice = !model.objects.empty() && !export_in_progress && model_fits && part_plate->has_printable_instances() - && !mixed_broken; + && !mixed_broken && part_plate->is_visible(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": can_slice %1%, model_fits= %2%, export_in_progress %3%, has_printable_instances %4%, mixed_broken %5% ")%can_slice %model_fits %export_in_progress %part_plate->has_printable_instances() %mixed_broken; main_frame->update_slice_print_status(MainFrame::eEventObjectUpdate, can_slice); @@ -10859,7 +10859,7 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": from set_current_panel, no_slice %1%, export_in_progress %2%, model_fits %3%, m_is_slicing %4%, mixed_broken %5%")%no_slice%export_in_progress%model_fits%m_is_slicing%mixed_broken; - if (!no_slice && !this->model.objects.empty() && !export_in_progress && model_fits && current_has_print_instances && !mixed_broken) + if (!no_slice && !this->model.objects.empty() && !export_in_progress && model_fits && current_has_print_instances && !mixed_broken && current_plate->is_visible()) { //if already running in background, not relice here //BBS: add more judge for slicing @@ -10874,7 +10874,7 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice) this->partplate_list.select_plate(plate_index); } } - else if (only_has_gcode_need_preview) + else if (only_has_gcode_need_preview && current_plate->is_visible()) { this->m_slice_all = false; this->q->reslice(); @@ -11786,6 +11786,7 @@ void Plater::priv::on_action_slice_plate(SimpleEvent&) { if (q != nullptr) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received slice plate event\n" ; + if (!q->get_partplate_list().get_curr_plate()->is_visible()) return; //BBS update extruder params and speed table before slicing const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config(); auto& print = q->get_partplate_list().get_current_fff_print(); @@ -22825,6 +22826,22 @@ int Plater::select_plate_by_hover_id(int hover_id, bool right_click, bool isModi ret = -1; } } + else if ((action == PartPlate::PLATE_VISIBLE_ID) && (!right_click)) + { + // toggle plate visibility + take_snapshot("toggle plate visibility"); + PartPlate* plate = p->partplate_list.get_plate(plate_index); + bool will_hide = plate && plate->is_visible(); + bool allowed = plate && (!will_hide || p->partplate_list.get_visible_plate_count() > 1); + if (allowed && p->partplate_list.get_plate_count() > 1) { + plate->set_visible(!plate->is_visible()); + p->partplate_list.update_unselected_plate_trans(p->partplate_list.get_plate_count()); + p->view3D->get_canvas3d()->update_plate_volumes_visibility(p->partplate_list); + p->sidebar->obj_list()->reload_all_plates(); + update(); + } + ret = 0; + } else if ((action == PartPlate::PLATE_FILAMENT_MAP_ID) && (!right_click)) { ret = select_plate(plate_index); if (!ret) {