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)