diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 794ebf08a..1a6c3b5b0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5273,7 +5273,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_volumes.volumes[m_hover_volume_id]->hover && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower) { // forces the selection of the volume - m_selection.add(m_hover_volume_id); + if (!m_selection.is_multiple_full_instance()) + m_selection.add(m_hover_volume_id); m_gizmos.update_on_off_state(m_selection); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); _update_gizmos_data(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2d96c35e7..a880b2cbe 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -51,6 +51,7 @@ ObjectList::ObjectList(wxWindow* parent) : create_object_popupmenu(&m_menu_object); create_part_popupmenu(&m_menu_part); create_sla_object_popupmenu(&m_menu_sla_object); + create_instance_popupmenu(&m_menu_instance); // describe control behavior Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) { @@ -400,15 +401,28 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) void ObjectList::show_context_menu() { + if (multiple_selection() && selected_instances_of_same_object()) + { + wxGetApp().plater()->PopupMenu(&m_menu_instance); + + wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { + evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId()); + return; + } + const auto item = GetSelection(); if (item) { - if (!(m_objects_model->GetItemType(item) & (itObject | itVolume))) + const ItemType type = m_objects_model->GetItemType(item); + if (!(type & (itObject | itVolume | itInstance))) return; - wxMenu* menu = m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : + + wxMenu* menu = type & itInstance ? &m_menu_instance : + m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : wxGetApp().plater()->printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; - append_menu_item_settings(menu); + if (!(type & itInstance)) + append_menu_item_settings(menu); wxGetApp().plater()->PopupMenu(menu); @@ -443,9 +457,11 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) { const wxDataViewItem item(event.GetItem()); - // only allow drags for item, not containers - if (multiple_selection() || GetSelection()!=item || - m_objects_model->GetParent(item) == wxDataViewItem(0)) { + const bool mult_sel = multiple_selection(); + + if (mult_sel && !selected_instances_of_same_object() || + !mult_sel && (GetSelection() != item || + m_objects_model->GetParent(item) == wxDataViewItem(0) ) ) { event.Veto(); return; } @@ -456,7 +472,17 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) return; } - m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), + if (mult_sel) + { + m_dragged_data.init(m_objects_model->GetObjectIdByItem(item),type); + std::set<int>& sub_obj_idxs = m_dragged_data.inst_idxs(); + wxDataViewItemArray sels; + GetSelections(sels); + for (auto sel : sels ) + sub_obj_idxs.insert(m_objects_model->GetInstanceIdByItem(sel)); + } + else + m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), type&itVolume ? m_objects_model->GetVolumeIdByItem(item) : m_objects_model->GetInstanceIdByItem(item), type); @@ -507,7 +533,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) if (m_dragged_data.type() == itInstance) { - instance_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.sub_obj_idx()); + instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); m_dragged_data.clear(); return; } @@ -759,6 +785,12 @@ wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu) } +wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "", + [this](wxCommandEvent&) { split_instances(); }, "", menu); +} + void ObjectList::create_object_popupmenu(wxMenu *menu) { append_menu_items_add_volume(menu); @@ -787,6 +819,11 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) menu->AppendSeparator(); } +void ObjectList::create_instance_popupmenu(wxMenu*menu) +{ + m_menu_item_split_instances = append_menu_item_instance_to_object(menu); +} + wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) { wxMenu *menu = new wxMenu; @@ -1134,6 +1171,27 @@ bool ObjectList::is_splittable() return splittable; } +bool ObjectList::selected_instances_of_same_object() +{ + wxDataViewItemArray sels; + GetSelections(sels); + + const int obj_idx = m_objects_model->GetObjectIdByItem(sels.front()); + + for (auto item : sels) { + if (! (m_objects_model->GetItemType(item) & itInstance) || + obj_idx != m_objects_model->GetObjectIdByItem(item)) + return false; + } + return true; +} + +bool ObjectList::can_split_instances() +{ + const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + return selection.is_multiple_full_instance() || selection.is_single_full_instance(); +} + void ObjectList::part_settings_changed() { m_part_settings_changed = true; @@ -1437,7 +1495,7 @@ bool ObjectList::multiple_selection() const void ObjectList::update_selections() { - auto& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; // We doesn't update selection if SettingsItem for the current object/part is selected @@ -1523,7 +1581,7 @@ void ObjectList::update_selections() void ObjectList::update_selections_on_canvas() { - auto& selection = wxGetApp().plater()->canvas3D()->get_selection(); + GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); const int sel_cnt = GetSelectedItemsCount(); if (sel_cnt == 0) { @@ -1747,23 +1805,41 @@ void ObjectList::update_settings_items() UnselectAll(); } -void ObjectList::instance_to_separated_object(const int obj_idx, const int inst_idx) +void ObjectList::instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idxs) { // create new object from selected instance ModelObject* model_object = (*m_objects)[obj_idx]->get_model()->add_object(*(*m_objects)[obj_idx]); - for (int i = model_object->instances.size() - 1; i >= 0; i--) + for (int inst_idx = model_object->instances.size() - 1; inst_idx >= 0; inst_idx--) { - if (i == inst_idx) + if (find(inst_idxs.begin(), inst_idxs.end(), inst_idx) != inst_idxs.end()) continue; - model_object->delete_instance(i); + model_object->delete_instance(inst_idx); } // Add new object to the object_list add_object_to_list(m_objects->size() - 1); - // delete selected instance from the object - del_subobject_from_object(obj_idx, inst_idx, itInstance); - delete_instance_from_list(obj_idx, inst_idx); + for (std::set<int>::const_reverse_iterator it = inst_idxs.rbegin(); it != inst_idxs.rend(); ++it) + { + // delete selected instance from the object + del_subobject_from_object(obj_idx, *it, itInstance); + delete_instance_from_list(obj_idx, *it); + } +} + +void ObjectList::split_instances() +{ + const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const int obj_idx = selection.get_object_idx(); + if (obj_idx == -1) + return; + + const int inst_idx = selection.get_instance_idx(); + const std::set<int> inst_idxs = inst_idx < 0 ? + selection.get_instance_idxs() : + std::set<int>{ inst_idx }; + + instances_to_separated_object(obj_idx, inst_idxs); } void ObjectList::ItemValueChanged(wxDataViewEvent &event) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 21db30ad5..f24eb95b0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -58,23 +58,34 @@ class ObjectList : public wxDataViewCtrl { void init(const int obj_idx, const int subobj_idx, const ItemType type) { m_obj_idx = obj_idx; - m_subobj_idx = subobj_idx; + m_type = type; + if (m_type&itVolume) + m_vol_idx = subobj_idx; + else + m_inst_idxs.insert(subobj_idx); + } + + void init(const int obj_idx, const ItemType type) { + m_obj_idx = obj_idx; m_type = type; } void clear() { m_obj_idx = -1; - m_subobj_idx = -1; + m_vol_idx = -1; + m_inst_idxs.clear(); m_type = itUndef; } int obj_idx() const { return m_obj_idx; } - int sub_obj_idx() const { return m_subobj_idx; } + int sub_obj_idx() const { return m_vol_idx; } ItemType type() const { return m_type; } + std::set<int>& inst_idxs() { return m_inst_idxs; } private: int m_obj_idx = -1; - int m_subobj_idx = -1; + int m_vol_idx = -1; + std::set<int> m_inst_idxs{}; ItemType m_type = itUndef; } m_dragged_data; @@ -96,9 +107,11 @@ class ObjectList : public wxDataViewCtrl wxMenu m_menu_object; wxMenu m_menu_part; wxMenu m_menu_sla_object; + wxMenu m_menu_instance; wxMenuItem* m_menu_item_split { nullptr }; wxMenuItem* m_menu_item_split_part { nullptr }; wxMenuItem* m_menu_item_settings { nullptr }; + wxMenuItem* m_menu_item_split_instances { nullptr }; std::vector<wxBitmap*> m_bmp_vector; @@ -153,9 +166,11 @@ public: wxMenuItem* append_menu_item_split(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); + wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); void create_object_popupmenu(wxMenu *menu); void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); + void create_instance_popupmenu(wxMenu*menu); wxMenu* create_settings_popupmenu(wxMenu *parent_menu); void update_opt_keys(t_config_option_keys& t_optopt_keys); @@ -171,6 +186,8 @@ public: void split(); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); bool is_splittable(); + bool selected_instances_of_same_object(); + bool can_split_instances(); wxPoint get_mouse_position_in_control(); wxBoxSizer* get_sizer() {return m_sizer;} @@ -227,7 +244,8 @@ public: bool has_multi_part_objects(); void update_settings_items(); - void instance_to_separated_object(const int obj_idx, const int inst_idx); + void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx); + void split_instances(); private: void OnChar(wxKeyEvent& event); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 17f5a2a63..24471e254 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2362,8 +2362,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ [this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png"); menu->AppendSeparator(); - wxMenuItem* item_instance_to_object = append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")) + dots, _(L("Set an Instance as a Separate Object")), - [this](wxCommandEvent&) { q->instance_to_separated_object(); }, ""); + wxMenuItem* item_instance_to_object = sidebar->obj_list()->append_menu_item_instance_to_object(menu); if (q != nullptr) { @@ -2773,16 +2772,6 @@ void Plater::set_number_of_copies(/*size_t num*/) decrease_instances(-diff); } -void Plater::instance_to_separated_object() -{ - const int obj_idx = p->get_selected_object_idx(); - const int inst_idx = p->get_selection().get_instance_idx(); - if (obj_idx == -1 || inst_idx == -1) - return; - - sidebar().obj_list()->instance_to_separated_object(obj_idx, inst_idx); -} - bool Plater::is_selection_empty() const { return p->get_selection().is_empty(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a0655ed55..7b19d6f31 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -136,7 +136,6 @@ public: void increase_instances(size_t num = 1); void decrease_instances(size_t num = 1); void set_number_of_copies(/*size_t num*/); - void instance_to_separated_object(); bool is_selection_empty() const; void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 52eb3180d..55c0b351f 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -406,7 +406,7 @@ void PrusaObjectDataViewModelNode::set_object_action_icon() { m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add_object.png")), wxBITMAP_TYPE_PNG); } void PrusaObjectDataViewModelNode::set_part_action_icon() { - m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(m_type == itVolume ? "cog.png" : "brick_go.png")), wxBITMAP_TYPE_PNG); } Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index fcce18e10..10709786c 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -275,6 +275,7 @@ public: else if (type == itInstance) { m_idx = parent->GetChildCount(); m_name = wxString::Format("Instance_%d", m_idx+1); + set_part_action_icon(); } }