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) {