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);