From ba8b81b27e03ec4dda329034b0d42e586b5d0f49 Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Thu, 1 Dec 2022 11:08:33 +0100
Subject: [PATCH] Cut: Extension for delete parts from cut objects.

When try to delete something from the cut object, than not just inform the users about an impossibility of this action,
but allow them to invalidate a cut information or delete all connectors from related objects, but leave the cut info.
---
 src/libslic3r/Model.cpp           |  16 ++++
 src/libslic3r/Model.hpp           |   4 +
 src/slic3r/GUI/GUI_ObjectList.cpp | 122 +++++++++++++++++++++++-------
 src/slic3r/GUI/GUI_ObjectList.hpp |   5 +-
 src/slic3r/GUI/Plater.cpp         |   2 +-
 5 files changed, 121 insertions(+), 28 deletions(-)

diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index b328dbf28..76668d0d2 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1279,6 +1279,14 @@ void ModelObject::invalidate_cut()
         volume->invalidate_cut_info();
 }
 
+void ModelObject::delete_connectors()
+{
+    for (int id = int(this->volumes.size()) - 1; id >= 0; id--) {
+        if (volumes[id]->is_cut_connector())
+            this->delete_volume(size_t(id));
+    }
+}
+
 void ModelObject::synchronize_model_after_cut()
 {
     for (ModelObject* obj : m_model->objects) {
@@ -1994,6 +2002,14 @@ int ModelObject::get_repaired_errors_count(const int vol_idx /*= -1*/) const
             stats.facets_reversed + stats.backwards_edges;
 }
 
+bool ModelObject::has_solid_mesh() const
+{
+    for (const ModelVolume* volume : volumes)
+        if (volume->is_model_part())
+            return true;
+    return false;
+}
+
 void ModelVolume::set_material_id(t_model_material_id material_id)
 {
     m_material_id = material_id;
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index d514171f1..6fb2bc923 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -449,6 +449,8 @@ public:
     void apply_cut_connectors(const std::string& name);
     // invalidate cut state for this object and its connectors/volumes
     void invalidate_cut();
+    // delete volumes which are marked as connector for this object
+    void delete_connectors();
     void synchronize_model_after_cut();
     void apply_cut_attributes(ModelObjectCutAttributes attributes);
     void clone_for_cut(ModelObject **obj);
@@ -482,6 +484,8 @@ public:
     // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
     int         get_repaired_errors_count(const int vol_idx = -1) const;
 
+    // Detect if object has at least one solid mash
+    bool has_solid_mesh() const;
     bool is_cut() const { return cut_id.id().valid(); }
     bool has_connectors() const;
 
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 127d97caa..8c1546410 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -1880,6 +1880,7 @@ bool ObjectList::del_subobject_item(wxDataViewItem& item)
 
     wxDataViewItem parent = m_objects_model->GetParent(item);
 
+    InfoItemType item_info_type = m_objects_model->GetInfoItemType(item);
     if (type & itSettings)
         del_settings_from_config(parent);
     else if (type & itInstanceRoot && obj_idx != -1)
@@ -1889,7 +1890,7 @@ bool ObjectList::del_subobject_item(wxDataViewItem& item)
     else if (type & itLayer && obj_idx != -1)
         del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item));
     else if (type & itInfo && obj_idx != -1)
-        del_info_item(obj_idx, m_objects_model->GetInfoItemType(item));
+        del_info_item(obj_idx, item_info_type);
     else if (idx == -1 || !del_subobject_from_object(obj_idx, idx, type))
         return false;
 
@@ -1898,9 +1899,12 @@ bool ObjectList::del_subobject_item(wxDataViewItem& item)
         const std::string& icon_name = get_warning_icon_name(object(obj_idx)->get_object_stl_stats());
         m_objects_model->UpdateWarningIcon(parent, icon_name);
     }
-    m_objects_model->Delete(item);
 
-    update_info_items(obj_idx);
+    if (!(type & itInfo) || item_info_type != InfoItemType::CutConnectors) {
+        // Connectors Item is already updated/deleted inside the del_info_item()
+        m_objects_model->Delete(item);
+        update_info_items(obj_idx);
+    }
 
     return true;
 }
@@ -1926,7 +1930,10 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
         break;
 
     case InfoItemType::CutConnectors:
-        show_error(nullptr, _L("Connectors cannot be deleted from cut object."));
+        if (!del_from_cut_object(true)) {
+            // there is no need to post EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS if nothing was changed
+            return; 
+        }
         break;
 
     case InfoItemType::MmuSegmentation:
@@ -2018,6 +2025,38 @@ void ObjectList::del_layers_from_object(const int obj_idx)
     changed_object(obj_idx);
 }
 
+bool ObjectList::del_from_cut_object(bool is_cut_connector, bool is_model_part/* = false*/, bool is_negative_volume/* = false*/)
+{
+    const long buttons_style = is_cut_connector   ? (wxYES | wxNO | wxCANCEL) : (wxYES | wxCANCEL);
+
+    const wxString title     = is_cut_connector   ? _L("Delete connector from object which is a part of cut") :
+                               is_model_part      ? _L("Delete solid part from object which is a part of cut") :
+                               is_negative_volume ? _L("Delete negative volume from object which is a part of cut") : "";
+                             
+    const wxString msg_end   = is_cut_connector   ? ("\n" + _L("To save cut correspondence you can delete all connectors from all related objects.")) : "";
+
+    InfoDialog dialog(wxGetApp().plater(), title,
+                      _L("This action will break a cut correspondence.\n"
+                         "After that PrusaSlicer can't guarantee model consistency.\n"
+                         "\n"
+                         "To manipulate with solid parts or negative volumes you have to invalidate cut infornation first." + msg_end ),
+                      false, buttons_style | wxCANCEL_DEFAULT | wxICON_WARNING);
+
+    dialog.SetButtonLabel(wxID_YES, _L("Invalidate cut info"));
+    if (is_cut_connector)
+        dialog.SetButtonLabel(wxID_NO, _L("Delete all connectors"));
+
+    const int answer = dialog.ShowModal();
+    if (answer == wxID_CANCEL)
+        return false;
+
+    if (answer == wxID_YES)
+        invalidate_cut_info_for_selection();
+    else if (answer == wxID_NO)
+        delete_all_connectors_for_selection();
+    return true;
+}
+
 bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type)
 {
     assert(idx >= 0);
@@ -2039,15 +2078,10 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
             Slic3r::GUI::show_error(nullptr, _L("From Object List You can't delete the last solid part from object."));
             return false;
         }
-        if (object->is_cut()) {
-            if (volume->is_model_part()) {
-                Slic3r::GUI::show_error(nullptr, _L("Solid part cannot be deleted from cut object."));
-                return false;
-            }
-            if (volume->is_negative_volume()) {
-                Slic3r::GUI::show_error(nullptr, _L("Negative volume cannot be deleted from cut object."));
-                return false;
-            }
+        if (object->is_cut() && (volume->is_model_part() || volume->is_negative_volume())) {
+            del_from_cut_object(volume->is_cut_connector(), volume->is_model_part(), volume->is_negative_volume());
+            // in any case return false to break the deletion
+            return false;
         }
 
         take_snapshot(_L("Delete Subobject"));
@@ -2489,6 +2523,7 @@ bool ObjectList::has_selected_cut_object() const
 
     return false;
 }
+
 void ObjectList::invalidate_cut_info_for_selection()
 {
     const wxDataViewItem item = GetSelection();
@@ -2499,27 +2534,61 @@ void ObjectList::invalidate_cut_info_for_selection()
     }
 }
 
-void ObjectList::invalidate_cut_info_for_object(size_t obj_idx)
+void ObjectList::invalidate_cut_info_for_object(int obj_idx)
 {
-    ModelObject* init_obj = object(int(obj_idx));
+    ModelObject* init_obj = object(obj_idx);
     if (!init_obj->is_cut())
         return;
 
     take_snapshot(_L("Invalidate cut info"));
 
-    auto invalidate_cut = [this](size_t obj_idx) {
-        object(int(obj_idx))->invalidate_cut();
-        update_info_items(obj_idx);
-        add_volumes_to_object_in_list(obj_idx);
-    };
-
+    const CutObjectBase cut_id = init_obj->cut_id;
     // invalidate cut for related objects (which have the same cut_id)
     for (size_t idx = 0; idx < m_objects->size(); idx++)
-        if (ModelObject* obj = object(idx); obj != init_obj && obj->cut_id.is_equal(init_obj->cut_id))
-            invalidate_cut(idx);
+        if (ModelObject* obj = object(int(idx)); obj->cut_id.is_equal(cut_id)) {
+            obj->invalidate_cut();
+            update_info_items(idx);
+            add_volumes_to_object_in_list(idx);
+        }
 
-    // invalidate own cut information
-    invalidate_cut(size_t(obj_idx));
+    update_lock_icons_for_model();
+}
+
+void ObjectList::delete_all_connectors_for_selection()
+{
+    const wxDataViewItem item = GetSelection();
+    if (item) {
+        const int obj_idx = m_objects_model->GetObjectIdByItem(item);
+        if (obj_idx >= 0)
+            delete_all_connectors_for_object(size_t(obj_idx));
+    }
+}
+
+void ObjectList::delete_all_connectors_for_object(int obj_idx)
+{
+    ModelObject* init_obj = object(obj_idx);
+    if (!init_obj->is_cut())
+        return;
+
+    take_snapshot(_L("Delete all connectors"));
+
+    const CutObjectBase cut_id = init_obj->cut_id;
+    // Delete all connectors for related objects (which have the same cut_id)
+    Model& model = wxGetApp().plater()->model();
+    for (int idx = int(m_objects->size())-1; idx >= 0; idx--)
+        if (ModelObject* obj = object(idx); obj->cut_id.is_equal(cut_id)) {
+            obj->delete_connectors();
+
+            if (obj->volumes.empty() || !obj->has_solid_mesh()) {
+                model.delete_object(idx);
+                m_objects_model->Delete(m_objects_model->GetItemById(idx));
+                continue;
+            }
+
+            update_info_items(idx);
+            add_volumes_to_object_in_list(idx);
+            changed_object(int(idx));
+        }
 
     update_lock_icons_for_model();
 }
@@ -3044,6 +3113,7 @@ bool ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
         return false;
 
     m_prevent_list_events = true;
+    ScopeGuard sg_prevent_list_events = ScopeGuard([this]() { m_prevent_list_events = false; });
 
     std::set<size_t> modified_objects_ids;
     for (std::vector<ItemForDelete>::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item) {
@@ -3059,7 +3129,7 @@ bool ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
         }
         else {
             if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type))
-                continue;
+                return false;// continue;
             if (item->type&itVolume) {
                 m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx));
                 ModelObject* obj = object(item->obj_idx);
diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp
index 9cd3dc8e1..d2965c77e 100644
--- a/src/slic3r/GUI/GUI_ObjectList.hpp
+++ b/src/slic3r/GUI/GUI_ObjectList.hpp
@@ -266,6 +266,7 @@ public:
     void                del_instances_from_object(const int obj_idx);
     void                del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range);
     void                del_layers_from_object(const int obj_idx);
+    bool                del_from_cut_object(bool is_connector, bool is_model_part = false, bool is_negative_volume = false);
     bool                del_subobject_from_object(const int obj_idx, const int idx, const int type);
     void                del_info_item(const int obj_idx, InfoItemType type);
     void                split();
@@ -282,7 +283,9 @@ public:
     bool                can_split_instances();
     bool                has_selected_cut_object() const;
     void                invalidate_cut_info_for_selection();
-    void                invalidate_cut_info_for_object(size_t obj_idx);
+    void                invalidate_cut_info_for_object(int obj_idx);
+    void                delete_all_connectors_for_selection();
+    void                delete_all_connectors_for_object(int obj_idx);
     bool                can_merge_to_multipart_object() const;
     bool                can_merge_to_single_object() const;
 
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index e11e08d04..e31f9ec61 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -3001,7 +3001,7 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx)
         InfoDialog dialog(q, _L("Delete object which is a part of cut object"), 
                              _L("You try to delete an object which is a part of a cut object.\n"
                                 "This action will break a cut correspondence.\n"
-                                "After that PrusaSlicer can't garantie model consistency"), 
+                                "After that PrusaSlicer can't guarantee model consistency"), 
                                 false, wxYES | wxCANCEL | wxCANCEL_DEFAULT | wxICON_WARNING);
         dialog.SetButtonLabel(wxID_YES, _L("Delete object"));
         if (dialog.ShowModal() == wxID_CANCEL)