From 463e9ab5300e74f1e665f5871ae446e0404c8453 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 11 Apr 2022 11:20:00 +0200 Subject: [PATCH] Cut WIP: + Added CutObjectBase class which contains cut attributes for object + ObjectList and ManipulationPanel : * Disable all ManipulationEditors for solid/negative volumes of cut object * Disable Scale/Size ManipulationEditors for objects/instances of objects which are CutParts of initial object + Scale/Rotation/Move gizmos are disabled for solid/negative volumes of cut object + Select whole CutParts of initial object when ScaleGizmo is active --- src/libslic3r/Model.cpp | 42 ++++++- src/libslic3r/Model.hpp | 6 + src/libslic3r/ObjectID.hpp | 35 ++++++ src/slic3r/GUI/GLCanvas3D.cpp | 5 + src/slic3r/GUI/GUI_ObjectList.cpp | 129 +++++++++++++++++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 3 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 16 +++ src/slic3r/GUI/GUI_ObjectManipulation.hpp | 5 + src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 6 + src/slic3r/GUI/Plater.cpp | 6 +- 12 files changed, 261 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ed341dfc66..829c76fb54 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,4 +1,5 @@ #include "Model.hpp" +#include "Model.hpp" #include "libslic3r.h" #include "BuildVolume.hpp" #include "Exception.hpp" @@ -610,6 +611,8 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->layer_height_profile = rhs.layer_height_profile; this->printable = rhs.printable; this->origin_translation = rhs.origin_translation; + this->cut_connectors_count = rhs.cut_connectors_count; + this->cut_id.copy(rhs.cut_id); m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; m_raw_bounding_box = rhs.m_raw_bounding_box; @@ -1369,12 +1372,17 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes) { + // discard old connector markers vor volumes + for (ModelVolume* volume : volumes) { + volume->source.is_connector = false; + } + if (cut_connectors.empty()) return; indexed_triangle_set connector_mesh = get_connector_mesh(connector_attributes); - size_t connector_id = 0; + size_t connector_id = cut_connectors_count; for (const CutConnector& connector : cut_connectors) { TriangleMesh mesh = TriangleMesh(connector_mesh); @@ -1393,10 +1401,20 @@ void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttr new_volume->source.is_connector = true; } + cut_connectors_count += cut_connectors.size(); // delete all connectors cut_connectors.clear(); } +void ModelObject::synchronize_model_after_cut() +{ + for (ModelObject* obj : m_model->objects) { + if (obj == this || obj->cut_id.is_equal(this->cut_id)) + continue; + obj->cut_id.set_check_sum(this->cut_id.check_sum()); + } +} + ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes) { if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) @@ -1404,6 +1422,18 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + // initiate/update cut attributes for object + if (cut_id.id().invalid()) + cut_id.init(); + { + int cut_obj_cnt = -1; + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::KeepLower)) cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) cut_obj_cnt++; + if (cut_obj_cnt > 0) + cut_id.increase_check_sum(size_t(cut_obj_cnt)); + } + // Clone the object to duplicate instances, materials etc. ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr; ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr; @@ -1499,10 +1529,16 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); } } - else + else { // Modifiers are not cut, but we still need to add the instance transformation // to the modifier volume transformation to preserve their shape properly. volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); + + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower->add_volume(*volume); + } } else if (!volume->mesh().empty() // && !volume->source.is_connector // we don't allow to cut a connectors @@ -1646,6 +1682,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + synchronize_model_after_cut(); + return res; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b3c5b47db1..6fa724162d 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -346,6 +346,9 @@ public: // Connectors to be added into the object after cut CutConnectors cut_connectors; + // count of connectors in object + size_t cut_connectors_count{ 0 }; + CutObjectBase cut_id; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation @@ -434,6 +437,7 @@ public: ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); void apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes); + void synchronize_model_after_cut(); ModelObjectPtrs cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs* new_objects); void merge(); @@ -458,6 +462,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; + bool is_cut() const { return cut_id.id().valid(); } + private: friend class Model; // This constructor assigns new ID to this ModelObject and its config. diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 1030171e7f..599c243b52 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -128,6 +128,41 @@ private: template void serialize(Archive &ar) { ar(m_timestamp); } }; +class CutObjectBase : public ObjectBase +{ + // check sum of CutPartsObject + size_t m_check_sum{ 1 }; + +public: + // Default Constructor to assign an invalid ID + CutObjectBase() : ObjectBase(-1) {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + CutObjectBase(int) : ObjectBase(-1) {} + // The class tree will have virtual tables and type information. + virtual ~CutObjectBase() = default; + + bool operator<(const CutObjectBase& other) const { return other.id() > this->id(); } + bool operator==(const CutObjectBase& other) const { return other.id() == this->id(); } + + void copy(const CutObjectBase& rhs) { + this->copy_id(rhs); + this->m_check_sum = rhs.check_sum(); + } + CutObjectBase operator=(const CutObjectBase& other) { + this->copy(other); + return *this; + } + + void init() { this->set_new_unique_id(); } + bool has_same_id(const CutObjectBase& rhs) { return this->id() == rhs.id(); } + bool is_equal(const CutObjectBase& rhs) { return this->id() == rhs.id() && this->check_sum() == rhs.check_sum(); } + + size_t check_sum() const { return m_check_sum; } + void set_check_sum(size_t cs) { m_check_sum = cs; } + void increase_check_sum(size_t cnt) { m_check_sum += cnt; } +}; + // Unique object / instance ID for the wipe tower. extern ObjectID wipe_tower_object_id(); extern ObjectID wipe_tower_instance_id(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 91add7c567..5fe2dab23d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3360,6 +3360,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) show_sinking_contours(); } } + else if (evt.LeftUp() && + m_gizmos.get_current_type() == GLGizmosManager::EType::Scale && + m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) { + wxGetApp().obj_list()->selection_changed(); + } #if ENABLE_OBJECT_MANIPULATOR_FOCUS handle_sidebar_focus_event("", false); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 7462e81534..3cb5bf3ce3 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -665,6 +665,8 @@ void ObjectList::selection_changed() fix_multiselection_conflicts(); + fix_cut_selection(); + // update object selection on Plater if (!m_prevent_canvas_selection_update) update_selections_on_canvas(); @@ -2437,6 +2439,9 @@ void ObjectList::part_selection_changed() bool update_and_show_settings = false; bool update_and_show_layers = false; + bool enable_manipulation {true}; + bool disable_ss_manipulation {false}; + const auto item = GetSelection(); if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { @@ -2445,6 +2450,43 @@ void ObjectList::part_selection_changed() const Selection& selection = scene_selection(); // don't show manipulation panel for case of all Object's parts selection update_and_show_manipulations = !selection.is_single_full_instance(); + + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) { + if (selection.is_any_volume() || selection.is_any_modifier()) + enable_manipulation = !(*m_objects)[obj_idx]->is_cut(); + else// if (item && m_objects_model->GetItemType(item) == itInstanceRoot) + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + } + else { + wxDataViewItemArray sels; + GetSelections(sels); + if (selection.is_single_full_object() || selection.is_multiple_full_instance() ) { + int obj_idx = m_objects_model->GetObjectIdByItem(sels.front()); + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + } + else if (selection.is_mixed() || selection.is_multiple_full_object()) { + std::map> cut_objects; + + // find cut objects + for (auto item : sels) { + int obj_idx = m_objects_model->GetObjectIdByItem(item); + const ModelObject* obj = object(obj_idx); + if (obj->is_cut()) { + if (cut_objects.find(obj->cut_id) == cut_objects.end()) + cut_objects[obj->cut_id] = std::set{ obj_idx }; + else + cut_objects.at(obj->cut_id).insert(obj_idx); + } + } + + // check if selected cut objects are "full selected" + for (auto cut_object : cut_objects) + if (cut_object.first.check_sum() != cut_object.second.size()) { + disable_ss_manipulation = true; + break; + } + } + } } else { if (item) { @@ -2486,6 +2528,8 @@ void ObjectList::part_selection_changed() default: { break; } } } + else + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); } else { if (type & itSettings) { @@ -2509,6 +2553,7 @@ void ObjectList::part_selection_changed() volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; + enable_manipulation = !(*m_objects)[obj_idx]->is_cut(); } else if (type & itInstance) { og_name = _L("Instance manipulation"); @@ -2516,6 +2561,7 @@ void ObjectList::part_selection_changed() // fill m_config by object's values m_config = &(*m_objects)[obj_idx]->config; + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); } else if (type & (itLayerRoot|itLayer)) { og_name = type & itLayerRoot ? _L("Height ranges") : _L("Settings for height range"); @@ -2538,6 +2584,11 @@ void ObjectList::part_selection_changed() wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item)); wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_info(obj_idx, volume_id)); } + + if (disable_ss_manipulation) + wxGetApp().obj_manipul()->DisableScale(); + else + wxGetApp().obj_manipul()->Enable(enable_manipulation); } if (update_and_show_settings) @@ -2554,6 +2605,7 @@ void ObjectList::part_selection_changed() panel.Freeze(); wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + wxGetApp().plater()->canvas3D()->enable_moving(enable_manipulation); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); @@ -3420,11 +3472,34 @@ void ObjectList::update_selections() if (sels.size() == 0 || m_selection_mode & smSettings) m_selection_mode = smUndef; - - select_items(sels); - // Scroll selected Item in the middle of an object list - ensure_current_item_visible(); + if (fix_cut_selection(sels)) { + m_prevent_list_events = true; + + // If some part is selected, unselect all items except of selected parts of the current object + UnselectAll(); + SetSelections(sels); + + m_prevent_list_events = false; + + // update object selection on Plater + if (!m_prevent_canvas_selection_update) + update_selections_on_canvas(); + + // to update the toolbar and info sizer + if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { + auto event = SimpleEvent(EVT_OBJ_LIST_OBJECT_SELECT); + event.SetEventObject(this); + wxPostEvent(this, event); + } + part_selection_changed(); + } + else { + select_items(sels); + + // Scroll selected Item in the middle of an object list + ensure_current_item_visible(); + } } void ObjectList::update_selections_on_canvas() @@ -3753,6 +3828,52 @@ void ObjectList::fix_multiselection_conflicts() m_prevent_list_events = false; } +bool ObjectList::fix_cut_selection(wxDataViewItemArray& sels) +{ + if (wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Scale) { + for (const auto& item : sels) { + if (m_objects_model->GetItemType(item) & (itInstance | itObject) || + (m_objects_model->GetItemType(item) & itSettings && + m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject)) { + + bool is_instance_selection = m_objects_model->GetItemType(item) & itInstance; + + int obj_idx = m_objects_model->GetObjectIdByItem(item); + int inst_idx = is_instance_selection ? m_objects_model->GetInstanceIdByItem(item) : 0; + + if (auto obj = object(obj_idx); obj->is_cut()) { + sels.Clear(); + + auto cut_id = obj->cut_id; + + for (int obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx) { + auto object = (*m_objects)[obj_idx]; + if (object->is_cut() && object->cut_id.has_same_id(cut_id)) + sels.Add(is_instance_selection ? m_objects_model->GetItemByInstanceId(obj_idx, inst_idx) : m_objects_model->GetItemById(obj_idx)); + } + return true; + } + } + } + } + return false; +} + +void ObjectList::fix_cut_selection() +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (fix_cut_selection(sels)) { + m_prevent_list_events = true; + + // If some part is selected, unselect all items except of selected parts of the current object + UnselectAll(); + SetSelections(sels); + + m_prevent_list_events = false; + } +} + ModelVolume* ObjectList::get_selected_model_volume() { wxDataViewItem item = GetSelection(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b9b816b7be..45072d4a50 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -353,6 +353,9 @@ public: bool check_last_selection(wxString& msg_str); // correct current selections to avoid of the possible conflicts void fix_multiselection_conflicts(); + // correct selection in respect to the cut_id if any exists + void fix_cut_selection(); + bool fix_cut_selection(wxDataViewItemArray& sels); ModelVolume* get_selected_model_volume(); void change_part_type(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6ab87150ba..089e89dfe8 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -465,6 +465,22 @@ void ObjectManipulation::UpdateAndShow(const bool show) OG_Settings::UpdateAndShow(show); } +void ObjectManipulation::Enable(const bool enadle) +{ + for (auto editor : m_editors) + editor->Enable(enadle); + for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt }) + win->Enable(enadle); +} + +void ObjectManipulation::DisableScale() +{ + for (auto editor : m_editors) + editor->Enable(editor->has_opt_key("scale") || editor->has_opt_key("size") ? false : true); + for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_lock_bnt }) + win->Enable(false); +} + void ObjectManipulation::update_ui_from_settings() { if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) { diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index a15c72fb8e..3a2eca2b65 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -57,6 +57,8 @@ public: void set_value(const wxString& new_value); void kill_focus(ObjectManipulation *parent); + bool has_opt_key(const std::string& key) { return m_opt_key == key; } + private: double get_value(); }; @@ -173,6 +175,9 @@ public: void Show(const bool show) override; bool IsShown() override; void UpdateAndShow(const bool show) override; + void Enable(const bool enadle = true); + void Disable() { Enable(false); } + void DisableScale(); void update_ui_from_settings(); bool use_colors() { return m_use_colors; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index fb1269d260..a172672eaa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -5,6 +5,7 @@ #if ENABLE_GL_SHADERS_ATTRIBUTES #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#include "libslic3r/Model.hpp" #include @@ -63,7 +64,13 @@ std::string GLGizmoMove3D::on_get_name() const bool GLGizmoMove3D::on_is_activable() const { - return !m_parent.get_selection().is_empty(); + const Selection& selection = m_parent.get_selection(); + if (selection.is_any_volume() || selection.is_any_modifier()) { + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) + return !m_parent.get_model()->objects[obj_idx]->is_cut(); + } + + return !selection.is_empty(); } void GLGizmoMove3D::on_start_dragging() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 6ab87e0253..6b61befcf2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Model.hpp" #include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" @@ -814,7 +815,13 @@ std::string GLGizmoRotate3D::on_get_name() const bool GLGizmoRotate3D::on_is_activable() const { - return !m_parent.get_selection().is_empty(); + const Selection& selection = m_parent.get_selection(); + if (selection.is_any_volume() || selection.is_any_modifier()) { + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) + return !m_parent.get_model()->objects[obj_idx]->is_cut(); + } + + return !selection.is_empty(); } void GLGizmoRotate3D::on_start_dragging() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 373a2396d8..be7c797bed 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -5,6 +5,7 @@ #if ENABLE_GL_SHADERS_ATTRIBUTES #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#include "libslic3r/Model.hpp" #include @@ -136,6 +137,11 @@ std::string GLGizmoScale3D::on_get_name() const bool GLGizmoScale3D::on_is_activable() const { const Selection& selection = m_parent.get_selection(); + if (selection.is_any_volume() || selection.is_any_modifier()) { + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) + return !m_parent.get_model()->objects[obj_idx]->is_cut(); + } + return !selection.is_empty() && !selection.is_wipe_tower(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b88cd7ce8f..bff535bed0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4860,7 +4860,8 @@ bool Plater::priv::can_increase_instances() const return false; int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) + && !model.objects[obj_idx]->is_cut(); } bool Plater::priv::can_decrease_instances() const @@ -4870,7 +4871,8 @@ bool Plater::priv::can_decrease_instances() const return false; int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1) + && !model.objects[obj_idx]->is_cut(); } bool Plater::priv::can_split_to_objects() const