diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg new file mode 100644 index 000000000..9b8430dd7 --- /dev/null +++ b/resources/icons/copy.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/copy_menu.svg b/resources/icons/copy_menu.svg new file mode 100644 index 000000000..0d1af6a0a --- /dev/null +++ b/resources/icons/copy_menu.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/delete_all_menu.svg b/resources/icons/delete_all_menu.svg new file mode 100644 index 000000000..5ee6d6ea6 --- /dev/null +++ b/resources/icons/delete_all_menu.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/paste.svg b/resources/icons/paste.svg new file mode 100644 index 000000000..028ffb8ea --- /dev/null +++ b/resources/icons/paste.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/resources/icons/paste_menu.svg b/resources/icons/paste_menu.svg new file mode 100644 index 000000000..74dbbf8ee --- /dev/null +++ b/resources/icons/paste_menu.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/resources/icons/remove_menu.svg b/resources/icons/remove_menu.svg new file mode 100644 index 000000000..a25ae965b --- /dev/null +++ b/resources/icons/remove_menu.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 6b52e2141..9038e388c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -721,32 +721,37 @@ int GLVolumeCollection::load_wipe_tower_preview( return int(this->volumes.size() - 1); } -typedef std::pair GLVolumeWithZ; -typedef std::vector GLVolumesWithZList; -static GLVolumesWithZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func) +GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func) { - GLVolumesWithZList list; + GLVolumeWithIdAndZList list; list.reserve(volumes.size()); - for (GLVolume* volume : volumes) + for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i) { + GLVolume* volume = volumes[i]; bool is_transparent = (volume->render_color[3] < 1.0f); if ((((type == GLVolumeCollection::Opaque) && !is_transparent) || ((type == GLVolumeCollection::Transparent) && is_transparent) || (type == GLVolumeCollection::All)) && (! filter_func || filter_func(*volume))) - list.emplace_back(std::make_pair(volume, 0.0)); + list.emplace_back(std::make_pair(volume, std::make_pair(i, 0.0))); } if ((type == GLVolumeCollection::Transparent) && (list.size() > 1)) { - for (GLVolumeWithZ& volume : list) + for (GLVolumeWithIdAndZ& volume : list) { - volume.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); + volume.second.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); } std::sort(list.begin(), list.end(), - [](const GLVolumeWithZ& v1, const GLVolumeWithZ& v2) -> bool { return v1.second < v2.second; } + [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.second.second < v2.second.second; } + ); + } + else if ((type == GLVolumeCollection::Opaque) && (list.size() > 1)) + { + std::sort(list.begin(), list.end(), + [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.first->selected && !v2.first->selected; } ); } @@ -788,9 +793,8 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool if (clipping_plane_id != -1) glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)clipping_plane)); - GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - - for (GLVolumeWithZ& volume : to_render) { + GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); + for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); volume.first->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id); } @@ -819,8 +823,8 @@ void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - for (GLVolumeWithZ& volume : to_render) + GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); + for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); volume.first->render_legacy(); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 5c50fb9aa..88547359e 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -412,6 +412,8 @@ public: }; typedef std::vector GLVolumePtrs; +typedef std::pair> GLVolumeWithIdAndZ; +typedef std::vector GLVolumeWithIdAndZList; class GLVolumeCollection { @@ -509,6 +511,8 @@ private: GLVolumeCollection& operator=(const GLVolumeCollection &); }; +GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func = nullptr); + class GLModel { protected: diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6a72698f6..19ce28bed 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3232,12 +3232,37 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.add_separator()) return false; + item.name = "copy"; +#if ENABLE_SVG_ICONS + item.icon_filename = "copy.svg"; +#endif // ENABLE_SVG_ICONS + item.tooltip = GUI::L_str("Copy") + " [" + GUI::shortkey_ctrl_prefix() + "C]"; + item.sprite_id = 4; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy(); }; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "paste"; +#if ENABLE_SVG_ICONS + item.icon_filename = "paste.svg"; +#endif // ENABLE_SVG_ICONS + item.tooltip = GUI::L_str("Paste") + " [" + GUI::shortkey_ctrl_prefix() + "V]"; + item.sprite_id = 5; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste(); }; + if (!m_toolbar.add_item(item)) + return false; + + if (!m_toolbar.add_separator()) + return false; + item.name = "more"; #if ENABLE_SVG_ICONS item.icon_filename = "instance_add.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Add instance [+]"); - item.sprite_id = 4; + item.sprite_id = 6; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; @@ -3249,7 +3274,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "instance_remove.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Remove instance [-]"); - item.sprite_id = 5; + item.sprite_id = 7; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; @@ -3264,7 +3289,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "split_objects.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Split to objects"); - item.sprite_id = 6; + item.sprite_id = 8; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; @@ -3276,7 +3301,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "split_parts.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Split to parts"); - item.sprite_id = 7; + item.sprite_id = 9; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; @@ -3291,7 +3316,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "layers.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Layers editing"); - item.sprite_id = 8; + item.sprite_id = 10; item.is_toggable = true; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; @@ -3501,7 +3526,7 @@ void GLCanvas3D::_picking_pass() const ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); ::glEnable(GL_CLIP_PLANE0); } - _render_volumes(true); + _render_volumes_for_picking(); if (! m_use_VBOs) ::glDisable(GL_CLIP_PLANE0); @@ -3702,13 +3727,10 @@ void GLCanvas3D::_render_legend_texture() const m_legend_texture.render(*this); } -void GLCanvas3D::_render_volumes(bool fake_colors) const +void GLCanvas3D::_render_volumes_for_picking() const { static const GLfloat INV_255 = 1.0f / 255.0f; - if (!fake_colors) - glsafe(::glEnable(GL_LIGHTING)); - // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); @@ -3718,27 +3740,31 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - unsigned int volume_id = 0; - for (GLVolume* vol : m_volumes.volumes) + const Transform3d& view_matrix = m_camera.get_view_matrix(); + GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Opaque, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) { - if (fake_colors) - { - // Object picking mode. Render the object with a color encoding the object index. - unsigned int r = (volume_id & 0x000000FF) >> 0; - unsigned int g = (volume_id & 0x0000FF00) >> 8; - unsigned int b = (volume_id & 0x00FF0000) >> 16; - glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - } - else - { - vol->set_render_color(); - glsafe(::glColor4fv(vol->render_color)); - } + // Object picking mode. Render the object with a color encoding the object index. + unsigned int r = (volume.second.first & 0x000000FF) >> 0; + unsigned int g = (volume.second.first & 0x0000FF00) >> 8; + unsigned int b = (volume.second.first & 0x00FF0000) >> 16; + glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - if ((!fake_colors || !vol->disabled) && (vol->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) - vol->render(); + if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) + volume.first->render(); + } - ++volume_id; + to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Transparent, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) + { + // Object picking mode. Render the object with a color encoding the object index. + unsigned int r = (volume.second.first & 0x000000FF) >> 0; + unsigned int g = (volume.second.first & 0x0000FF00) >> 8; + unsigned int b = (volume.second.first & 0x00FF0000) >> 16; + glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); + + if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) + volume.first->render(); } glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); @@ -3746,9 +3772,6 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const glsafe(::glDisable(GL_BLEND)); glsafe(::glEnable(GL_CULL_FACE)); - - if (!fake_colors) - glsafe(::glDisable(GL_LIGHTING)); } void GLCanvas3D::_render_current_gizmo() const @@ -4026,15 +4049,9 @@ void GLCanvas3D::_update_volumes_hover_state() const return; GLVolume* volume = m_volumes.volumes[m_hover_volume_id]; - - switch (m_selection.get_mode()) - { - case Selection::Volume: - { + if (volume->is_modifier) volume->hover = true; - break; - } - case Selection::Instance: + else { int object_idx = volume->object_idx(); int instance_idx = volume->instance_idx(); @@ -4044,9 +4061,6 @@ void GLCanvas3D::_update_volumes_hover_state() const if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) v->hover = true; } - - break; - } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ef128bae4..2486753e7 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -609,7 +609,7 @@ private: #endif // ENABLE_RENDER_SELECTION_CENTER void _render_warning_texture() const; void _render_legend_texture() const; - void _render_volumes(bool fake_colors) const; + void _render_volumes_for_picking() const; void _render_current_gizmo() const; void _render_gizmos_overlay() const; void _render_toolbar() const; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 144d02476..842700aef 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -21,6 +21,8 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 0f8b17e04..24314d60f 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -20,6 +20,8 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 7db6d16b8..2a1d328e1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -439,6 +439,66 @@ void ObjectList::selection_changed() part_selection_changed(); } +void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) +{ + if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) + return; + + if (volumes.empty()) + return; + + ModelObject& model_object = *(*m_objects)[obj_idx]; + const auto object_item = m_objects_model->GetItemById(obj_idx); + + wxDataViewItemArray items; + + for (const ModelVolume* volume : volumes) + { + auto vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(), + volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); + auto opt_keys = volume->config.keys(); + if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) + select_item(m_objects_model->AddSettingsChild(vol_item)); + + items.Add(vol_item); + } + + m_parts_changed = true; + parts_changed(obj_idx); + + if (items.size() > 1) + { + m_selection_mode = smVolume; + m_last_selected_item = wxDataViewItem(0); + } + + select_items(items); +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ +} + +void ObjectList::paste_objects_into_list(const std::vector& object_idxs) +{ + if (object_idxs.empty()) + return; + + wxDataViewItemArray items; + for (const size_t object : object_idxs) + { + add_object_to_list(object); + m_parts_changed = true; + parts_changed(object); + + items.Add(m_objects_model->GetItemById(object)); + } + + select_items(items); +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ +} + void ObjectList::OnChar(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_BACK){ @@ -1576,11 +1636,11 @@ void ObjectList::split() for (auto id = 0; id < model_object->volumes.size(); id++) { const auto vol_item = m_objects_model->AddVolumeChild(parent, from_u8(model_object->volumes[id]->name), - model_object->volumes[id]->is_modifier() ? - ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, - model_object->volumes[id]->config.has("extruder") ? - model_object->volumes[id]->config.option("extruder")->value : 0, - false); + model_object->volumes[id]->is_modifier() ? + ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, + model_object->volumes[id]->config.has("extruder") ? + model_object->volumes[id]->config.option("extruder")->value : 0, + false); // add settings to the part, if it has those auto opt_keys = model_object->volumes[id]->config.keys(); if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { @@ -2135,6 +2195,7 @@ void ObjectList::update_selections_on_canvas() add_to_selection(item, selection, instance_idx, false); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); + wxGetApp().plater()->canvas3D()->render(); } void ObjectList::select_item(const wxDataViewItem& item) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 2f465a4ab..a0343100a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -31,6 +31,8 @@ typedef std::map> FreqSettingsBundle; // category -> vector ( option ; label ) typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; +typedef std::vector ModelVolumePtrs; + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); @@ -285,6 +287,10 @@ public: void rename_item(); void fix_through_netfabb() const; void update_item_error_icon(const int obj_idx, int vol_idx) const ; + + void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); + void paste_objects_into_list(const std::vector& object_idxs); + private: void OnChar(wxKeyEvent& event); void OnContextMenu(wxDataViewEvent &event); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 87145511c..a6b5ed6be 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -375,13 +375,22 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { m_plater->select_all(); }, ""); editMenu->AppendSeparator(); wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete, _(L("Deletes the current selection")), - [this](wxCommandEvent&) { m_plater->remove_selected(); }, ""); + [this](wxCommandEvent&) { m_plater->remove_selected(); }, "remove_menu"); wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete, _(L("Deletes all objects")), - [this](wxCommandEvent&) { m_plater->reset(); }, ""); + [this](wxCommandEvent&) { m_plater->reset(); }, "delete_all_menu"); + + editMenu->AppendSeparator(); + + wxMenuItem* item_copy = append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + "\tCtrl+C", _(L("Copy selection to clipboard")), + [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, "copy_menu"); + wxMenuItem* item_paste = append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + "\tCtrl+V", _(L("Paste clipboard")), + [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, "paste_menu"); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete_sel->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_all()); }, item_delete_all->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater->can_copy()); }, item_copy->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater->can_paste()); }, item_paste->GetId()); } // Window menu diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 960d90801..001f7bb6d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1405,6 +1405,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); @@ -3699,6 +3701,34 @@ void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) void Plater::update_object_menu() { p->update_object_menu(); } +void Plater::copy_selection_to_clipboard() +{ + p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); +} + +void Plater::paste_from_clipboard() +{ + p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); +} + +bool Plater::can_paste_from_clipboard() const +{ + const Selection& selection = p->view3D->get_canvas3d()->get_selection(); + const Selection::Clipboard& clipboard = selection.get_clipboard(); + Selection::EMode mode = clipboard.get_mode(); + + if (clipboard.is_empty()) + return false; + + if ((mode == Selection::Volume) && !selection.is_from_single_instance()) + return false; + + if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance)) + return false; + + return true; +} + bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } @@ -3707,5 +3737,7 @@ bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } bool Plater::can_arrange() const { return p->can_arrange(); } bool Plater::can_layers_editing() const { return p->can_layers_editing(); } +bool Plater::can_copy() const { return !is_selection_empty(); } +bool Plater::can_paste() const { return can_paste_from_clipboard(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index f830edce3..708c07f39 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -182,6 +182,10 @@ public: PrinterTechnology printer_technology() const; void set_printer_technology(PrinterTechnology printer_technology); + void copy_selection_to_clipboard(); + void paste_from_clipboard(); + bool can_paste_from_clipboard() const; + bool can_delete() const; bool can_delete_all() const; bool can_increase_instances() const; @@ -190,6 +194,8 @@ public: bool can_split_to_volumes() const; bool can_arrange() const; bool can_layers_editing() const; + bool can_copy() const; + bool can_paste() const; private: struct priv; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index dedf55a45..8ee449a21 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1022,6 +1022,70 @@ bool Selection::requires_local_axes() const return (m_mode == Volume) && is_from_single_instance(); } +void Selection::copy_to_clipboard() +{ + if (!m_valid) + return; + + m_clipboard.reset(); + + for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) + { + ModelObject* src_object = m_model->objects[object.first]; + ModelObject* dst_object = m_clipboard.add_object(); + dst_object->name = src_object->name; + dst_object->input_file = src_object->input_file; + dst_object->config = src_object->config; + + for (int i : object.second) + { + dst_object->add_instance(*src_object->instances[i]); + } + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + if ((volume->object_idx() == object.first) && (volume->instance_idx() == *object.second.begin())) + { + int volume_idx = volume->volume_idx(); + if ((0 <= volume_idx) && (volume_idx < (int)src_object->volumes.size())) + { + ModelVolume* src_volume = src_object->volumes[volume_idx]; + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + dst_volume->config = src_volume->config; + } + } + } + } + + m_clipboard.set_mode(m_mode); +} + +void Selection::paste_from_clipboard() +{ + if (!m_valid || m_clipboard.is_empty()) + return; + + switch (m_clipboard.get_mode()) + { + case Volume: + { + if (is_from_single_instance()) + paste_volumes_from_clipboard(); + + break; + } + case Instance: + { + if (m_mode == Instance) + paste_objects_from_clipboard(); + + break; + } + } +} + void Selection::update_valid() { m_valid = (m_volumes != nullptr) && (m_model != nullptr); @@ -1697,5 +1761,43 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); } +void Selection::paste_volumes_from_clipboard() +{ + int obj_idx = get_object_idx(); + if ((obj_idx < 0) || ((int)m_model->objects.size() <= obj_idx)) + return; + + ModelObject* src_object = m_clipboard.get_object(0); + if (src_object != nullptr) + { + ModelObject* dst_object = m_model->objects[obj_idx]; + + ModelVolumePtrs volumes; + for (ModelVolume* src_volume : src_object->volumes) + { + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->config = src_volume->config; + dst_volume->set_new_unique_id(); + dst_volume->translate(10.0, 10.0, 0.0); + volumes.push_back(dst_volume); + } + wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); + } +} + +void Selection::paste_objects_from_clipboard() +{ + std::vector object_idxs; + const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); + for (const ModelObject* src_object : src_objects) + { + ModelObject* dst_object = m_model->add_object(*src_object); + dst_object->translate(10.0, 10.0, 0.0); + object_idxs.push_back(m_model->objects.size() - 1); + } + + wxGetApp().obj_list()->paste_objects_into_list(object_idxs); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index b03a8e89a..a8b0c06dc 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -138,6 +138,23 @@ public: typedef std::set InstanceIdxsList; typedef std::map ObjectIdxsToInstanceIdxsMap; + class Clipboard + { + Model m_model; + Selection::EMode m_mode; + + public: + void reset() { m_model.clear_objects(); } + bool is_empty() const { return m_model.objects.empty(); } + + ModelObject* add_object() { return m_model.add_object(); } + ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; } + const ModelObjectPtrs& get_objects() const { return m_model.objects; } + + Selection::EMode get_mode() const { return m_mode; } + void set_mode(Selection::EMode mode) { m_mode = mode; } + }; + private: struct Cache { @@ -163,6 +180,7 @@ private: // set of indices to m_volumes IndicesList m_list; Cache m_cache; + Clipboard m_clipboard; mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_dirty; @@ -267,6 +285,11 @@ public: bool requires_local_axes() const; + void copy_to_clipboard(); + void paste_from_clipboard(); + + const Clipboard& get_clipboard() const { return m_clipboard; } + private: void update_valid(); void update_type(); @@ -301,6 +324,9 @@ private: void synchronize_unselected_volumes(); void ensure_on_bed(); bool is_from_fully_selected_instance(unsigned int volume_idx) const; + + void paste_volumes_from_clipboard(); + void paste_objects_from_clipboard(); }; } // namespace GUI diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 768360482..6e078617d 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -529,10 +529,10 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int ext } wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item, - const wxString &name, - const Slic3r::ModelVolumeType volume_type, - const int extruder/* = 0*/, - const bool create_frst_child/* = true*/) + const wxString &name, + const Slic3r::ModelVolumeType volume_type, + const int extruder/* = 0*/, + const bool create_frst_child/* = true*/) { PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); @@ -545,7 +545,7 @@ wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &pa insert_position = -1; if (create_frst_child && root->m_volumes_cnt == 0) - { + { const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 4a3482abc..b935448d9 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -454,12 +454,12 @@ public: ~PrusaObjectDataViewModel(); wxDataViewItem Add(const wxString &name, const int extruder); - wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, - const wxString &name, - const Slic3r::ModelVolumeType volume_type, - const int extruder = 0, - const bool create_frst_child = true); - wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); + wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, + const wxString &name, + const Slic3r::ModelVolumeType volume_type, + const int extruder = 0, + const bool create_frst_child = true); + wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num);