From ba8b81b27e03ec4dda329034b0d42e586b5d0f49 Mon Sep 17 00:00:00 2001 From: YuSanka 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& it return false; m_prevent_list_events = true; + ScopeGuard sg_prevent_list_events = ScopeGuard([this]() { m_prevent_list_events = false; }); std::set modified_objects_ids; for (std::vector::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& 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)