From 8fd6194403f8522c05182051be06d8e3ec10a815 Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Wed, 23 Jan 2019 16:01:37 +0100
Subject: [PATCH] Improved Instance splitting : - Added icon and context menu
 for Instance. - Added multiple selection and splitting for the instances (add
 new object with selected instances)

---
 src/slic3r/GUI/GLCanvas3D.cpp     |   3 +-
 src/slic3r/GUI/GUI_ObjectList.cpp | 110 +++++++++++++++++++++++++-----
 src/slic3r/GUI/GUI_ObjectList.hpp |  28 ++++++--
 src/slic3r/GUI/Plater.cpp         |  13 +---
 src/slic3r/GUI/Plater.hpp         |   1 -
 src/slic3r/GUI/wxExtensions.cpp   |   2 +-
 src/slic3r/GUI/wxExtensions.hpp   |   1 +
 7 files changed, 121 insertions(+), 37 deletions(-)

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();
         }
 	}