From ac6a232795c1b78f600423012bb0cf5ead6c2fe1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 29 Mar 2019 14:36:09 +0100 Subject: [PATCH 01/23] Improved Selection from ObjectList side --- src/slic3r/GUI/GUI_ObjectList.cpp | 102 ++++++++++++++++++---- src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 +- src/slic3r/GUI/Selection.hpp | 2 + src/slic3r/GUI/wxExtensions.cpp | 26 ++++++ src/slic3r/GUI/wxExtensions.hpp | 2 +- 6 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 224bb802e..4c46e7135 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1639,16 +1639,20 @@ void ObjectList::part_selection_changed() bool update_and_show_manipulations = false; bool update_and_show_settings = false; - if (multiple_selection()) { + const auto item = GetSelection(); + + if ( multiple_selection() || item && m_objects_model->GetItemType(item) == itInstanceRoot ) + { og_name = _(L("Group manipulation")); - update_and_show_manipulations = true; + + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + // don't show manipulation panel for case of all Object's parts selection + update_and_show_manipulations = !selection.is_single_full_instance(); } else { - const auto item = GetSelection(); if (item) { - bool is_part = false; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { obj_idx = m_objects_model->GetIdByItem(item); og_name = _(L("Object manipulation")); @@ -1666,7 +1670,6 @@ void ObjectList::part_selection_changed() } else { og_name = _(L("Part Settings to modify")); - is_part = true; auto main_parent = m_objects_model->GetParent(parent); obj_idx = m_objects_model->GetIdByItem(main_parent); const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); @@ -1676,7 +1679,6 @@ void ObjectList::part_selection_changed() } else if (m_objects_model->GetItemType(item) == itVolume) { og_name = _(L("Part manipulation")); - is_part = true; const auto volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; @@ -1924,6 +1926,15 @@ bool ObjectList::multiple_selection() const return GetSelectedItemsCount() > 1; } +bool ObjectList::is_selected(const ItemType type) const +{ + const wxDataViewItem& item = GetSelection(); + if (item) + return m_objects_model->GetItemType(item) == type; + + return false; +} + void ObjectList::update_selections() { const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); @@ -1942,25 +1953,64 @@ void ObjectList::update_selections() return; } } - - if (selection.is_single_full_object()) +// if (selection.is_single_full_object() && selection.get_instance_idx() != -1) +// { +// sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); +// } +// else if (selection.is_single_volume() || selection.is_modifier() || +// selection.is_multiple_volume() || selection.is_multiple_full_object()) +// { +// for (auto idx : selection.get_volume_idxs()) { +// const auto gl_vol = selection.get_volume(idx); +// if (selection.is_multiple_full_object()) +// sels.Add(m_objects_model->GetItemById(gl_vol->object_idx())); +// else if (gl_vol->volume_idx() >= 0) +// // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids +// // are not associated with ModelVolumes, but they are temporarily generated by the backend +// // (for example, SLA supports or SLA pad). +// sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); +// } +// } + else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { - sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); + const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content(); + for (const auto& object : objects_content) { + if (object.second.size() == 1) // object with 1 instance + sels.Add(m_objects_model->GetItemById(object.first)); + else if (object.second.size() > 1) // object with several instances + { + wxDataViewItemArray current_sels; + GetSelections(current_sels); + const wxDataViewItem frst_inst_item = m_objects_model->GetItemByInstanceId(object.first, 0); + + bool root_is_selected = false; + for (const auto& item:current_sels) + if (item == m_objects_model->GetParent(frst_inst_item)) { + root_is_selected = true; + break; + } + if (root_is_selected) + continue; + + const Selection::InstanceIdxsList& instances = object.second; + for (const auto& inst : instances) + sels.Add(m_objects_model->GetItemByInstanceId(object.first, inst)); + } + } } - else if (selection.is_single_volume() || selection.is_modifier() || - selection.is_multiple_volume() || selection.is_multiple_full_object()) { + else if (selection.is_single_volume() || selection.is_modifier() || selection.is_multiple_volume()) + { for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); - if (selection.is_multiple_full_object()) - sels.Add(m_objects_model->GetItemById(gl_vol->object_idx())); - else if (gl_vol->volume_idx() >= 0) + if (gl_vol->volume_idx() >= 0) // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids // are not associated with ModelVolumes, but they are temporarily generated by the backend // (for example, SLA supports or SLA pad). sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); } } - else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) { + else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) + { for (auto idx : selection.get_instance_idxs()) { sels.Add(m_objects_model->GetItemByInstanceId(selection.get_object_idx(), idx)); } @@ -2136,11 +2186,25 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); - for (auto item : sels) { - if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) + for (const auto item : sels) { + if (!IsSelected(item)) // if this item is unselected now (from previous actions) + continue; + + if (m_objects_model->GetItemType(item) & itSettings) { Unselect(item); - else if (m_objects_model->GetParent(item) != wxDataViewItem(0)) - Unselect(m_objects_model->GetParent(item)); + continue; + } + + const wxDataViewItem& parent = m_objects_model->GetParent(item); + if (parent != wxDataViewItem(0) && IsSelected(parent)) + Unselect(parent); + else + { + wxDataViewItemArray unsels; + m_objects_model->GetAllChildren(item, unsels); + for (const auto unsel_item : unsels) + Unselect(unsel_item); + } } m_prevent_list_events = false; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 4ddc6ea71..b0ed756f8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -251,6 +251,7 @@ public: void init_objects(); bool multiple_selection() const ; + bool is_selected(const ItemType type) const; void update_selections(); void update_selections_on_canvas(); void select_item(const wxDataViewItem& item); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6c8fdcab7..637596591 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -161,6 +161,8 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_move_label_string = L("Position"); m_new_rotate_label_string = L("Rotation"); m_new_scale_label_string = L("Scale factors"); + + ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one @@ -187,7 +189,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_enabled = true; } - else if (selection.is_single_full_object()) + else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) { m_cache.instance.reset(); @@ -212,7 +214,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_size = volume->get_volume_transformation().get_matrix(true, true) * volume->bounding_box.size(); m_new_enabled = true; } - else if (wxGetApp().obj_list()->multiple_selection()) + else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { reset_settings_value(); m_new_move_label_string = L("Translate"); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 200f239c1..6390214cd 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -133,10 +133,12 @@ private: const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; } }; +public: typedef std::map VolumesCache; typedef std::set InstanceIdxsList; typedef std::map ObjectIdxsToInstanceIdxsMap; +private: struct Cache { // Cache of GLVolume derived transformation matrices, valid during mouse dragging. diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 55544f28e..77d9a4a10 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1239,6 +1239,32 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, return count; } +void PrusaObjectDataViewModel::GetAllChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node) { + for (auto object : m_objects) + array.Add(wxDataViewItem((void*)object)); + } + else if (node->GetChildCount() == 0) + return; + else { + const size_t count = node->GetChildren().GetCount(); + for (size_t pos = 0; pos < count; pos++) { + PrusaObjectDataViewModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + } + + wxDataViewItemArray new_array = array; + for (const auto item : new_array) + { + wxDataViewItemArray children; + GetAllChildren(item, children); + WX_APPEND_ARRAY(array, children); + } +} + ItemType PrusaObjectDataViewModel::GetItemType(const wxDataViewItem &item) const { if (!item.IsOk()) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 27da67deb..72221962c 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -511,7 +511,7 @@ public: virtual bool IsContainer(const wxDataViewItem &item) const override; virtual unsigned int GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const override; - + void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const; // Is the container just a header or an item with all columns // In our case it is an item with all columns virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } From 81809018bc9087cb563fc74591eac4a7309a91f6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 2 Apr 2019 16:33:52 +0200 Subject: [PATCH 02/23] Selection improvements + "Delete" menu_item for Instances under OSX --- src/slic3r/GUI/GUI_ObjectList.cpp | 158 ++++++++++++++++++++++-------- src/slic3r/GUI/GUI_ObjectList.hpp | 10 ++ 2 files changed, 129 insertions(+), 39 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4c46e7135..b61b8280f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -82,13 +82,14 @@ ObjectList::ObjectList(wxWindow* parent) : init_icons(); // describe control behavior - Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) { + Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) { #ifndef __APPLE__ // On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called // before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object // manipulator cache with the following call to selection_changed() wxGetApp().obj_manipul()->emulate_kill_focus(); #endif // __APPLE__ + m_last_selected_item = event.GetItem(); selection_changed(); #ifndef __WXMSW__ set_tooltip_for_item(get_mouse_position_in_control()); @@ -1006,8 +1007,7 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu) void ObjectList::append_menu_items_osx(wxMenu* menu) { - append_menu_item(menu, wxID_ANY, _(L("Delete item")), "", - [this](wxCommandEvent&) { remove(); }, "", menu); + append_menu_item_delete(menu); append_menu_item(menu, wxID_ANY, _(L("Rename")), "", [this](wxCommandEvent&) { rename_item(); }, "", menu); @@ -1062,6 +1062,12 @@ void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const } } +void ObjectList::append_menu_item_delete(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _(L("Delete")), "", + [this](wxCommandEvent&) { remove(); }, "", menu); +} + void ObjectList::create_object_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ @@ -1118,6 +1124,9 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) void ObjectList::create_instance_popupmenu(wxMenu*menu) { +#ifdef __WXOSX__ + append_menu_item_delete(menu); +#endif // __WXOSX__ m_menu_item_split_instances = append_menu_item_instance_to_object(menu); wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { @@ -1552,7 +1561,7 @@ void ObjectList::split() auto opt_keys = model_object->volumes[id]->config.keys(); if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { select_item(m_objects_model->AddSettingsChild(vol_item)); - Collapse(vol_item); + /*Collapse*/Expand(vol_item); } } @@ -1746,7 +1755,7 @@ void ObjectList::add_object_to_list(size_t obj_idx) auto opt_keys = model_object->volumes[id]->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(vol_item)); - Collapse(vol_item); + /*Collapse*/Expand(vol_item); } } Expand(item); @@ -1760,7 +1769,7 @@ void ObjectList::add_object_to_list(size_t obj_idx) auto opt_keys = model_object->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(item)); - Collapse(item); + /*Collapse*/Expand(item); } #ifndef __WXOSX__ @@ -1940,6 +1949,8 @@ void ObjectList::update_selections() const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; + m_selection_mode = smInstance; + // We doesn't update selection if SettingsItem for the current object/part is selected if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) { @@ -1953,24 +1964,6 @@ void ObjectList::update_selections() return; } } -// if (selection.is_single_full_object() && selection.get_instance_idx() != -1) -// { -// sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); -// } -// else if (selection.is_single_volume() || selection.is_modifier() || -// selection.is_multiple_volume() || selection.is_multiple_full_object()) -// { -// for (auto idx : selection.get_volume_idxs()) { -// const auto gl_vol = selection.get_volume(idx); -// if (selection.is_multiple_full_object()) -// sels.Add(m_objects_model->GetItemById(gl_vol->object_idx())); -// else if (gl_vol->volume_idx() >= 0) -// // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids -// // are not associated with ModelVolumes, but they are temporarily generated by the backend -// // (for example, SLA supports or SLA pad). -// sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); -// } -// } else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content(); @@ -1985,8 +1978,10 @@ void ObjectList::update_selections() bool root_is_selected = false; for (const auto& item:current_sels) - if (item == m_objects_model->GetParent(frst_inst_item)) { + if (item == m_objects_model->GetParent(frst_inst_item) || + item == m_objects_model->GetTopParent(frst_inst_item)) { root_is_selected = true; + sels.Add(item); break; } if (root_is_selected) @@ -2008,6 +2003,7 @@ void ObjectList::update_selections() // (for example, SLA supports or SLA pad). sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); } + m_selection_mode = smVolume; } else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) { @@ -2017,7 +2013,7 @@ void ObjectList::update_selections() } else if (selection.is_mixed()) { - auto& objects_content_list = selection.get_content(); + const Selection::ObjectIdxsToInstanceIdxsMap& objects_content_list = selection.get_content(); for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); @@ -2050,6 +2046,9 @@ void ObjectList::update_selections() sels.Add(m_objects_model->GetItemByVolumeId(glv_obj_idx, glv_vol_idx)); } } + + if (sels.size() == 0) + m_selection_mode = smUndef; select_items(sels); @@ -2085,29 +2084,34 @@ void ObjectList::update_selections_on_canvas() auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) { - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { - selection.add_object(m_objects_model->GetIdByItem(item), as_single_selection); + const ItemType& type = m_objects_model->GetItemType(item); + if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { + wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item; + selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection); return; } - if (m_objects_model->GetItemType(item) == itVolume) { + if (type == itVolume) { const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); const int vol_idx = m_objects_model->GetVolumeIdByItem(item); selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); } - else if (m_objects_model->GetItemType(item) == itInstance) { + else if (type == itInstance) { const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int inst_idx = m_objects_model->GetInstanceIdByItem(item); selection.add_instance(obj_idx, inst_idx, as_single_selection); } }; + // stores current instance idx before to clear the selection + int instance_idx = selection.get_instance_idx(); + if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) - add_to_selection(m_objects_model->GetParent(item), selection, -1, true); + add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); else - add_to_selection(item, selection, -1, true); + add_to_selection(item, selection, instance_idx, true); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); return; @@ -2116,8 +2120,6 @@ void ObjectList::update_selections_on_canvas() wxDataViewItemArray sels; GetSelections(sels); - // stores current instance idx before to clear the selection - int instance_idx = selection.get_instance_idx(); selection.clear(); for (auto item: sels) add_to_selection(item, selection, instance_idx, false); @@ -2163,22 +2165,79 @@ void ObjectList::select_item_all_children() if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { for (int i = 0; i < m_objects->size(); i++) sels.Add(m_objects_model->GetItemById(i)); + m_selection_mode = smInstance; } else { const auto item = GetSelection(); // Some volume(instance) is selected => select all volumes(instances) inside the current object - if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) { + if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) m_objects_model->GetChildren(m_objects_model->GetParent(item), sels); - } + + m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance; } SetSelections(sels); selection_changed(); } +// update selection mode for non-multiple selection +void ObjectList::update_selection_mode() +{ + m_last_selected_item = wxDataViewItem(0); + + // All items are unselected + if (!GetSelection()) + { + m_selection_mode = smUndef; + return; + } + + const ItemType type = m_objects_model->GetItemType(GetSelection()); + m_selection_mode = type&itSettings ? smUndef : + type&itVolume ? smVolume : smInstance; +} + +// check last selected item. If is it possible to select it +bool ObjectList::check_last_selection() +{ + if (!m_last_selected_item) + return true; + + /* We can't mix Parts and Objects/Instances. + * So, unselect last selected item and show information about it + */ + const ItemType type = m_objects_model->GetItemType(m_last_selected_item); + if (type & itSettings || + type & itVolume && m_selection_mode == smInstance || + !(type & itVolume) && m_selection_mode == smVolume) + { + Unselect(m_last_selected_item); + + // Inform user why selection isn't complited + const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); + + const wxString msg_str = wxString::Format(_(L("You started your selection with %s Item.")) + "\n" + + _(L("In this mode you can select only another %s Items%s")), item_type, item_type, + m_selection_mode == smInstance ? "." : " " + _(L("of a current Object"))); + + wxMessageDialog dialog(this, _(L("Unsupported selection")) + "\n\n" + msg_str, + _(L("Info")), wxICON_INFORMATION); + dialog.ShowModal(); + + return false; + } + + return true; +} + void ObjectList::fix_multiselection_conflicts() { - if (GetSelectedItemsCount() <= 1) + if (GetSelectedItemsCount() <= 1) { + update_selection_mode(); + return; + } + + if (!check_last_selection()) return; m_prevent_list_events = true; @@ -2186,6 +2245,8 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); + m_selection_mode = smInstance; + for (const auto item : sels) { if (!IsSelected(item)) // if this item is unselected now (from previous actions) continue; @@ -2205,6 +2266,26 @@ void ObjectList::fix_multiselection_conflicts() for (const auto unsel_item : unsels) Unselect(unsel_item); } + + if (m_objects_model->GetItemType(item) == itVolume) + { + // If some part is selected, unselect all items except of selected parts of the current object + sels.clear(); + + wxDataViewItemArray children; // selected volumes from current parent + m_objects_model->GetChildren(parent, children); + for (const auto child : children) + { + if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) + sels.Add(child); + } + + UnselectAll(); + SetSelections(sels); + + m_selection_mode = smVolume; + break; + } } m_prevent_list_events = false; @@ -2489,8 +2570,7 @@ void ObjectList::show_multi_selection_menu() wxMenu* menu = new wxMenu(); #ifdef __WXOSX__ - append_menu_item(menu, wxID_ANY, _(L("Delete items")), "", - [this](wxCommandEvent&) { remove(); }, "", menu); + append_menu_item_delete(); #endif //__WXOSX__ if (extruders_count() > 1) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b0ed756f8..0611cc4f1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -60,6 +60,12 @@ struct ItemForDelete class ObjectList : public wxDataViewCtrl { + enum SELECTION_MODE + { + smUndef, + smVolume, + smInstance + } m_selection_mode {smUndef}; struct dragged_item_data { @@ -135,6 +141,7 @@ class ObjectList : public wxDataViewCtrl bool m_part_settings_changed = false; int m_selected_row = 0; + wxDataViewItem m_last_selected_item {nullptr}; #if 0 FreqSettingsBundle m_freq_settings_fff; @@ -188,6 +195,7 @@ public: void append_menu_item_fix_through_netfabb(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu) const ; void append_menu_item_change_extruder(wxMenu* menu) const; + void append_menu_item_delete(wxMenu* menu); void create_object_popupmenu(wxMenu *menu); void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); @@ -258,6 +266,8 @@ public: void select_items(const wxDataViewItemArray& sels); void select_all(); void select_item_all_children(); + void update_selection_mode(); + bool check_last_selection(); // correct current selections to avoid of the possible conflicts void fix_multiselection_conflicts(); From fed0f189d3de2cf4439b7aea429b4422f076450c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 3 Apr 2019 08:39:36 +0200 Subject: [PATCH 03/23] Fixed typo --- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b61b8280f..203e76573 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2570,7 +2570,7 @@ void ObjectList::show_multi_selection_menu() wxMenu* menu = new wxMenu(); #ifdef __WXOSX__ - append_menu_item_delete(); + append_menu_item_delete(menu); #endif //__WXOSX__ if (extruders_count() > 1) From a36bdefda51833f7c3ac63d2ce1870fcea671d34 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Apr 2019 09:20:11 +0200 Subject: [PATCH 04/23] Code to load SVG icons into the BitmapCache class. --- resources/icons/layers.svg | 40 +++++++++--------- src/slic3r/GUI/BitmapCache.cpp | 73 +++++++++++++++++++++++++++++++++ src/slic3r/GUI/BitmapCache.hpp | 6 +++ src/slic3r/GUI/GLTexture.cpp | 5 +-- src/slic3r/GUI/Tab.cpp | 2 +- src/slic3r/GUI/wxExtensions.cpp | 23 +++++------ 6 files changed, 113 insertions(+), 36 deletions(-) diff --git a/resources/icons/layers.svg b/resources/icons/layers.svg index 7718a8cbd..da5dec21d 100644 --- a/resources/icons/layers.svg +++ b/resources/icons/layers.svg @@ -1,25 +1,27 @@ - + - + viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 16baa1629..4c7f999ff 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -1,5 +1,7 @@ #include "BitmapCache.hpp" +#include "libslic3r/Utils.hpp" + #if ! defined(WIN32) && ! defined(__APPLE__) #define BROKEN_ALPHA #endif @@ -9,6 +11,11 @@ #include #endif /* BROKEN_ALPHA */ +#define NANOSVG_IMPLEMENTATION +#include "nanosvg/nanosvg.h" +#define NANOSVGRAST_IMPLEMENTATION +#include "nanosvg/nanosvgrast.h" + namespace Slic3r { namespace GUI { void BitmapCache::clear() @@ -155,6 +162,72 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg #endif } +wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned int width, unsigned int height, const unsigned char *raw_data) +{ + wxImage image(width, height); + image.InitAlpha(); + unsigned char *rgb = image.GetData(); + unsigned char *alpha = image.GetAlpha(); + unsigned int pixels = width * height; + for (unsigned int i = 0; i < pixels; ++ i) { + *rgb ++ = *raw_data ++; + *rgb ++ = *raw_data ++; + *rgb ++ = *raw_data ++; + *alpha ++ = *raw_data ++; + } + return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); +} + +wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned int height) +{ + std::string bitmap_key = bitmap_name + "-h" + std::to_string(height); + auto it = m_map.find(bitmap_key); + if (it != m_map.end()) + return it->second; + + wxImage image; + if (! image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) || + image.GetWidth() == 0 || image.GetHeight() == 0) + return nullptr; + if (image.GetHeight() != height) + image.Rescale(int(0.5f + float(image.GetWidth()) * height / image.GetHeight()), height, wxIMAGE_QUALITY_BILINEAR); + return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); +} + +wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned int target_height) +{ + std::string bitmap_key = bitmap_name + "-h" + std::to_string(target_height); + auto it = m_map.find(bitmap_key); + if (it != m_map.end()) + return it->second; + + NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f); + if (image == nullptr) + return nullptr; + + float scale = (float)target_height / image->height; + int width = (int)(scale * image->width + 0.5f); + int height = (int)(scale * image->height + 0.5f); + int n_pixels = width * height; + if (n_pixels <= 0) { + ::nsvgDelete(image); + return nullptr; + } + + NSVGrasterizer *rast = ::nsvgCreateRasterizer(); + if (rast == nullptr) { + ::nsvgDelete(image); + return nullptr; + } + + std::vector data(n_pixels * 4, 0); + ::nsvgRasterize(rast, image, 0, 0, scale, data.data(), width, height, width * 4); + ::nsvgDeleteRasterizer(rast); + ::nsvgDelete(image); + + return this->insert_raw_rgba(bitmap_key, width, height, data.data()); +} + wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency) { wxImage image(width, height); diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 0cb70d28b..ce5eb3c77 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -29,6 +29,12 @@ public: wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); wxBitmap* insert(const std::string &name, const std::vector &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); } wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end); + wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned int width, unsigned int height, const unsigned char *raw_data); + + // Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height if nonzero. + wxBitmap* load_png(const std::string &bitmap_key, unsigned int height = 0); + // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height. + wxBitmap* load_svg(const std::string &bitmap_key, unsigned int height); static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency); static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index b48ca2044..68369d9d0 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -11,15 +11,11 @@ #include #include -#define NANOSVG_IMPLEMENTATION #include "nanosvg/nanosvg.h" -#define NANOSVGRAST_IMPLEMENTATION #include "nanosvg/nanosvgrast.h" #include "libslic3r/Utils.hpp" -#include "libslic3r/Utils.hpp" - namespace Slic3r { namespace GUI { @@ -378,6 +374,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns if (n_pixels <= 0) { reset(); + nsvgDelete(image); return false; } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 631050f29..a91dae026 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1446,7 +1446,7 @@ void TabFilament::build() line.append_option(optgroup->get_option("bed_temperature")); optgroup->append_line(line); - page = add_options_page(_(L("Cooling")), "hourglass.png"); + page = add_options_page(_(L("Cooling")), "cooling"); optgroup = page->new_optgroup(_(L("Enable"))); optgroup->append_single_option_line("fan_always_on"); optgroup->append_single_option_line("cooling"); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 55544f28e..09096cb05 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include "BitmapCache.hpp" #include "GUI.hpp" #include "GUI_App.hpp" @@ -421,19 +423,16 @@ void PrusaCollapsiblePaneMSW::Collapse(bool collapse) // PrusaObjectDataViewModelNode // ---------------------------------------------------------------------------- -wxBitmap create_scaled_bitmap(const std::string& bmp_name) +wxBitmap create_scaled_bitmap(const std::string& bmp_name_in) { - const double scale_f = Slic3r::GUI::wxGetApp().em_unit()* 0.1;//GetContentScaleFactor(); - if (scale_f == 1.0) - return wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(bmp_name)), wxBITMAP_TYPE_PNG); -// else if (scale_f == 2.0) // use biger icon -// return wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(bmp_name_X2)), wxBITMAP_TYPE_PNG); - - wxImage img = wxImage(Slic3r::GUI::from_u8(Slic3r::var(bmp_name)), wxBITMAP_TYPE_PNG); - const int sz_w = int(img.GetWidth()*scale_f); - const int sz_h = int(img.GetHeight()*scale_f); - img.Rescale(sz_w, sz_h, wxIMAGE_QUALITY_BILINEAR); - return wxBitmap(img); + static Slic3r::GUI::BitmapCache cache; + const auto height = (unsigned int)(Slic3r::GUI::wxGetApp().em_unit() * 1.6f + 0.5f); + std::string bmp_name = bmp_name_in; + boost::replace_last(bmp_name, ".png", ""); + wxBitmap *bmp = cache.load_svg(bmp_name, height); + if (bmp == nullptr) + bmp = cache.load_png(bmp_name, height); + return *bmp; } void PrusaObjectDataViewModelNode::set_object_action_icon() { From 936f7a3b844813efe6dec8e75117c1e9cf5ab258 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 4 Apr 2019 09:35:13 +0200 Subject: [PATCH 05/23] Select newly added parts/volumes from current selected instance when adding from 3D scene's context menu --- src/slic3r/GUI/GUI_ObjectList.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c0dcc659d..c725fae43 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2051,12 +2051,15 @@ void ObjectList::update_selections_on_canvas() } }; + // stores current instance idx before to clear the selection + int instance_idx = selection.get_instance_idx(); + if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) add_to_selection(m_objects_model->GetParent(item), selection, -1, true); else - add_to_selection(item, selection, -1, true); + add_to_selection(item, selection, instance_idx, true); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); return; @@ -2065,8 +2068,6 @@ void ObjectList::update_selections_on_canvas() wxDataViewItemArray sels; GetSelections(sels); - // stores current instance idx before to clear the selection - int instance_idx = selection.get_instance_idx(); selection.clear(); for (auto item: sels) add_to_selection(item, selection, instance_idx, false); From 30f44880d7f3b76e503a0c329f87ee84eff6c139 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 4 Apr 2019 11:31:26 +0200 Subject: [PATCH 06/23] Removed 'Export print config' checkbox from save dialog for 3mf and amf files --- src/slic3r/GUI/Plater.cpp | 70 +++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 435d9548f..bc9b7d5b5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1199,7 +1199,7 @@ struct Plater::priv BoundingBox scaled_bed_shape_bb() const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); - std::unique_ptr get_export_file(GUI::FileType file_type); + wxString get_export_file(GUI::FileType file_type); const Selection& get_selection() const; Selection& get_selection(); @@ -1784,7 +1784,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode return obj_idxs; } -std::unique_ptr Plater::priv::get_export_file(GUI::FileType file_type) +wxString Plater::priv::get_export_file(GUI::FileType file_type) { wxString wildcard; switch (file_type) { @@ -1804,31 +1804,43 @@ std::unique_ptr Plater::priv::get_export_file(GUI::FileType // Find the file name of the first printable object. fs::path output_file = this->model.propose_export_file_name_and_path(); + wxString dlg_title; switch (file_type) { - case FT_STL: output_file.replace_extension("stl"); break; - case FT_AMF: output_file.replace_extension("zip.amf"); break; // XXX: Problem on OS X with double extension? - case FT_3MF: output_file.replace_extension("3mf"); break; + case FT_STL: + { + output_file.replace_extension("stl"); + dlg_title = _(L("Export STL file:")); + break; + } + case FT_AMF: + { + // XXX: Problem on OS X with double extension? + output_file.replace_extension("zip.amf"); + dlg_title = _(L("Export AMF file:")); + break; + } + case FT_3MF: + { + output_file.replace_extension("3mf"); + dlg_title = _(L("Save file as:")); + break; + } default: break; } - auto dlg = Slic3r::make_unique(q, - ((file_type == FT_AMF) || (file_type == FT_3MF)) ? _(L("Export print config")) : "", - true, - _(L("Save file as:")), - from_path(output_file.parent_path()), - from_path(output_file.filename()), - wildcard, - wxFD_SAVE | wxFD_OVERWRITE_PROMPT - ); + wxFileDialog* dlg = new wxFileDialog(q, dlg_title, + from_path(output_file.parent_path()), from_path(output_file.filename()), + wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg->ShowModal() != wxID_OK) { - return nullptr; + return wxEmptyString; } - fs::path path(into_path(dlg->GetPath())); + wxString out_path = dlg->GetPath(); + fs::path path(into_path(out_path)); wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); - return dlg; + return out_path; } const Selection& Plater::priv::get_selection() const @@ -3243,11 +3255,8 @@ void Plater::export_stl(bool selection_only) { if (p->model.objects.empty()) { return; } - auto dialog = p->get_export_file(FT_STL); - if (! dialog) { return; } - - // Store a binary STL - const wxString path = dialog->GetPath(); + wxString path = p->get_export_file(FT_STL); + if (path.empty()) { return; } const std::string path_u8 = into_u8(path); wxBusyCursor wait; @@ -3272,15 +3281,14 @@ void Plater::export_amf() { if (p->model.objects.empty()) { return; } - auto dialog = p->get_export_file(FT_AMF); - if (! dialog) { return; } - - const wxString path = dialog->GetPath(); + wxString path = p->get_export_file(FT_AMF); + if (path.empty()) { return; } const std::string path_u8 = into_u8(path); - DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); wxBusyCursor wait; - if (Slic3r::store_amf(path_u8.c_str(), &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) { + bool export_config = true; + DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); + if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { // Success p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path)); } else { @@ -3297,10 +3305,8 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) bool export_config = true; if (output_path.empty()) { - auto dialog = p->get_export_file(FT_3MF); - if (!dialog) { return; } - path = dialog->GetPath(); - export_config = dialog->get_checkbox_value(); + path = p->get_export_file(FT_3MF); + if (path.empty()) { return; } } else path = from_path(output_path); From 369cdd8b3ba61f975e59682b4615d2b93855dc48 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 4 Apr 2019 12:02:13 +0200 Subject: [PATCH 07/23] Ask user to switch to expert mode when loading a 3mf or an amf file containing instances or modifiers from simple mode --- src/slic3r/GUI/Plater.cpp | 41 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bc9b7d5b5..9cf9768a5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1617,6 +1617,45 @@ std::vector Plater::priv::load_files(const std::vector& input_ } #if ENABLE_VOLUMES_CENTERING_FIXES } + else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf)) + { + bool advanced = false; + for (const ModelObject* model_object : model.objects) + { + // is there more than one instance ? + if (model_object->instances.size() > 1) + { + advanced = true; + break; + } + + // is there any modifier ? + for (const ModelVolume* model_volume : model_object->volumes) + { + if (!model_volume->is_model_part()) + { + advanced = true; + break; + } + } + + if (advanced) + break; + } + + if (advanced) + { + wxMessageDialog dlg(q, _(L("This file cannot be loaded in simple mode. Do you want to switch to expert mode?\n")), + _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + { + Slic3r::GUI::wxGetApp().save_mode(comExpert); + view3D->set_as_dirty(); + } + else + return obj_idxs; + } + } #endif // ENABLE_VOLUMES_CENTERING_FIXES #if !ENABLE_VOLUMES_CENTERING_FIXES @@ -1642,7 +1681,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ Slic3r::GUI::show_error(nullptr, wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")), from_path(filename))); - return std::vector(); + return obj_idxs; } } From 590ae25b13d2ff27843a82f821ec6b2d9e2e12f6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 4 Apr 2019 12:30:11 +0200 Subject: [PATCH 08/23] Altering sla export interface to support explicit project name. --- src/libslic3r/Print.cpp | 2 +- src/libslic3r/PrintExport.hpp | 19 ++++++++++--------- src/libslic3r/SLAPrint.hpp | 20 ++++++++------------ src/libslic3r/Zipper.cpp | 5 ----- src/libslic3r/Zipper.hpp | 3 --- 5 files changed, 19 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 794390133..c13f0bc2a 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -10,7 +10,7 @@ #include "GCode/WipeTowerPrusaMM.hpp" #include "Utils.hpp" -#include "PrintExport.hpp" +//#include "PrintExport.hpp" #include #include diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index 04b993a52..ce62f7cb0 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -7,6 +7,7 @@ #include #include +#include #include "Rasterizer/Rasterizer.hpp" //#include @@ -72,7 +73,8 @@ public: void finish_layer(); // Save all the layers into the file (or dir) specified in the path argument - void save(const std::string& path); + // An optional project name can be added to be used for the layer file names + void save(const std::string& path, const std::string& projectname = ""); // Save only the selected layer to the file specified in path argument. void save_layer(unsigned lyr, const std::string& path); @@ -86,7 +88,8 @@ template struct VeryFalse { static const bool value = false; }; template class LayerWriter { public: - LayerWriter(const std::string& /*zipfile_path*/) { + LayerWriter(const std::string& /*zipfile_path*/) + { static_assert(VeryFalse::value, "No layer writer implementation provided!"); } @@ -99,10 +102,6 @@ public: void binary_entry(const std::string& /*fname*/, const std::uint8_t* buf, size_t len); - // Get the name of the archive but only the name part without the path or - // the extension. - std::string get_name() { return ""; } - // Test whether the object can still be used for writing. bool is_ok() { return false; } @@ -253,12 +252,14 @@ public: } template - inline void save(const std::string& path) { + inline void save(const std::string& fpath, const std::string& prjname = "") + { try { - LayerWriter writer(path); + LayerWriter writer(fpath); if(!writer.is_ok()) return; - std::string project = writer.get_name(); + std::string project = prjname.empty()? + boost::filesystem::path(fpath).stem().string() : prjname; writer.next_entry("config.ini"); if(!writer.is_ok()) return; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a1e382acb..d4443d915 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -320,10 +320,8 @@ struct SLAPrintStatistics } }; -struct SLAminzZipper {}; - // The implementation of creating zipped archives with wxWidgets -template<> class LayerWriter { +template<> class LayerWriter { Zipper m_zip; public: @@ -332,16 +330,12 @@ public: void next_entry(const std::string& fname) { m_zip.add_entry(fname); } void binary_entry(const std::string& fname, - const std::uint8_t* buf, - size_t l) + const std::uint8_t* buf, + size_t l) { m_zip.add_entry(fname, buf, l); } - std::string get_name() const { - return m_zip.get_name(); - } - template inline LayerWriter& operator<<(T&& arg) { m_zip << std::forward(arg); return *this; } @@ -389,9 +383,11 @@ public: // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } - template - void export_raster(const std::string& fname) { - if(m_printer) m_printer->save(fname); + template + inline void export_raster(const std::string& fpath, + const std::string& projectname = "") + { + if(m_printer) m_printer->save(fpath, projectname); } const PrintObjects& objects() const { return m_objects; } diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 6b7faaddc..4466f1b04 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -4,7 +4,6 @@ #include "Zipper.hpp" #include "miniz/miniz_zip.h" -#include #include #include "I18N.hpp" @@ -213,10 +212,6 @@ void Zipper::finish_entry() m_entry.clear(); } -std::string Zipper::get_name() const { - return boost::filesystem::path(m_impl->m_zipname).stem().string(); -} - void Zipper::finalize() { finish_entry(); diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index 6566dad42..7d95ffdac 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -81,9 +81,6 @@ public: /// file is up to minz after the erroneous write. void finish_entry(); - /// Gets the name of the archive without the path or extension. - std::string get_name() const; - void finalize(); }; From 251747e6faffa2426acb6bb43ba071e288abc49c Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 4 Apr 2019 10:28:41 +0200 Subject: [PATCH 09/23] GLGizmoCut: Scale input field --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 02d663e93..5eb0d0583 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -15,12 +15,6 @@ namespace Slic3r { namespace GUI { - - - - -// GLGizmoCut - class GLGizmoCutPanel : public wxPanel { public: @@ -192,7 +186,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co m_imgui->set_next_window_bg_alpha(0.5f); m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - ImGui::PushItemWidth(100.0f); + ImGui::PushItemWidth(m_imgui->scaled(5.0f)); bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); From 70ce79c86e6131862b59e3fc2124d6c6166f9cea Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 4 Apr 2019 12:31:09 +0200 Subject: [PATCH 10/23] SLA export: Finalize filename when exporting & uploading, set correct project name when uploading --- src/slic3r.cpp | 5 +++-- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 11 ++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 958b66305..780efea7b 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -397,8 +397,9 @@ int CLI::run(int argc, char **argv) outfile_final = fff_print.print_statistics().finalize_output_path(outfile); } else { outfile = sla_print.output_filepath(outfile); - sla_print.export_raster(outfile); - outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata + outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + sla_print.export_raster(outfile_final); } if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 2601842ef..c6a73864d 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -98,8 +98,9 @@ void BackgroundSlicingProcess::process_sla() m_print->process(); if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { - m_sla_print->export_raster(m_export_path); - m_print->set_status(100, "Masked SLA file exported to " + m_export_path); + const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); + m_sla_print->export_raster(export_path); + m_print->set_status(100, "Masked SLA file exported to " + export_path); } else if (! m_upload_job.empty()) { prepare_upload(); } else { @@ -389,7 +390,7 @@ void BackgroundSlicingProcess::prepare_upload() // Generate a unique temp path to which the gcode/zip file is copied/exported boost::filesystem::path source_path = boost::filesystem::temp_directory_path() - / boost::filesystem::unique_path(".printhost.%%%%-%%%%-%%%%-%%%%.gcode"); + / boost::filesystem::unique_path(".Slic3rPE.upload.%%%%-%%%%-%%%%-%%%%"); if (m_print == m_fff_print) { m_print->set_status(95, "Running post-processing scripts"); @@ -399,8 +400,8 @@ void BackgroundSlicingProcess::prepare_upload() run_post_process_scripts(source_path.string(), m_fff_print->config()); m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); } else { - m_sla_print->export_raster(source_path.string()); - // TODO: Also finalize upload path like with FFF when there are statistics for SLA print + m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); + m_sla_print->export_raster(source_path.string(), m_upload_job.upload_data.upload_path.string()); } m_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str()); From 6197f48321f9a7646c88d39d8a8c76a59f56543a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 4 Apr 2019 14:00:31 +0200 Subject: [PATCH 11/23] Use current selection to determine proposed filename when exporting to stl files --- src/libslic3r/Model.cpp | 36 +++++++++++++++++++++++------------- src/libslic3r/Model.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 14 ++++++++++++-- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ba898d9d5..e634dd138 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -556,19 +556,9 @@ std::string Model::propose_export_file_name_and_path() const for (const ModelObject *model_object : this->objects) for (ModelInstance *model_instance : model_object->instances) if (model_instance->is_printable()) { - input_file = model_object->input_file; - if (! model_object->name.empty()) { - if (input_file.empty()) - // model_object->input_file was empty, just use model_object->name - input_file = model_object->name; - else { - // Replace file name in input_file with model_object->name, but keep the path and file extension. - input_file = (boost::filesystem::path(model_object->name).parent_path().empty()) ? - (boost::filesystem::path(input_file).parent_path() / model_object->name).make_preferred().string() : - model_object->name; - } - } - if (! input_file.empty()) + input_file = model_object->get_export_filename(); + + if (!input_file.empty()) goto end; // Other instances will produce the same name, skip them. break; @@ -1433,6 +1423,26 @@ void ModelObject::print_info() const cout << "volume = " << mesh.volume() << endl; } +std::string ModelObject::get_export_filename() const +{ + std::string ret = input_file; + + if (!name.empty()) + { + if (ret.empty()) + // input_file was empty, just use name + ret = name; + else + { + // Replace file name in input_file with name, but keep the path and file extension. + ret = (boost::filesystem::path(name).parent_path().empty()) ? + (boost::filesystem::path(ret).parent_path() / name).make_preferred().string() : name; + } + } + + return ret; +} + 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 5cf7f49ca..951401243 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -275,6 +275,8 @@ public: // Print object statistics to console. void print_info() const; + std::string get_export_filename() const; + protected: friend class Print; friend class SLAPrint; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9cf9768a5..53d999283 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1840,8 +1840,18 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) // Update printbility state of each of the ModelInstances. this->update_print_volume_state(); - // Find the file name of the first printable object. - fs::path output_file = this->model.propose_export_file_name_and_path(); + + const Selection& selection = get_selection(); + int obj_idx = selection.get_object_idx(); + + fs::path output_file; + // first try to get the file name from the current selection + if ((0 <= obj_idx) && (obj_idx < (int)this->model.objects.size())) + output_file = this->model.objects[obj_idx]->get_export_filename(); + + if (output_file.empty()) + // Find the file name of the first printable object. + output_file = this->model.propose_export_file_name_and_path(); wxString dlg_title; switch (file_type) { From 09054a0bc9de788c646d35e0d5cd10c1b28c8abd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 4 Apr 2019 15:07:54 +0200 Subject: [PATCH 12/23] Fixed non-consistency of the selection --- src/slic3r/GUI/GUI_ObjectList.cpp | 149 ++++++++++++++++++++---------- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- 2 files changed, 100 insertions(+), 51 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 203e76573..f495e168a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -89,7 +89,24 @@ ObjectList::ObjectList(wxWindow* parent) : // manipulator cache with the following call to selection_changed() wxGetApp().obj_manipul()->emulate_kill_focus(); #endif // __APPLE__ - m_last_selected_item = event.GetItem(); + + /* For multiple selection with pressed SHIFT, + * event.GetItem() returns value of a first item in selection list + * instead of real last clicked item. + * So, let check last selected item in such strange way + */ + if (wxGetKeyState(WXK_SHIFT)) + { + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.front() == m_last_selected_item) + m_last_selected_item = sels.back(); + else + m_last_selected_item = event.GetItem(); + } + else + m_last_selected_item = event.GetItem(); + selection_changed(); #ifndef __WXMSW__ set_tooltip_for_item(get_mouse_position_in_control()); @@ -511,7 +528,7 @@ void ObjectList::key_event(wxKeyEvent& event) printf("WXK_BACK\n"); remove(); } - else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT)) + else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) select_item_all_children(); else event.Skip(); @@ -2183,11 +2200,10 @@ void ObjectList::select_item_all_children() // update selection mode for non-multiple selection void ObjectList::update_selection_mode() { - m_last_selected_item = wxDataViewItem(0); - // All items are unselected if (!GetSelection()) { + m_last_selected_item = wxDataViewItem(0); m_selection_mode = smUndef; return; } @@ -2198,33 +2214,55 @@ void ObjectList::update_selection_mode() } // check last selected item. If is it possible to select it -bool ObjectList::check_last_selection() +bool ObjectList::check_last_selection(wxString& msg_str) { if (!m_last_selected_item) return true; + + const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT); /* We can't mix Parts and Objects/Instances. - * So, unselect last selected item and show information about it + * So, show information about it */ const ItemType type = m_objects_model->GetItemType(m_last_selected_item); - if (type & itSettings || + + // check a case of a selection of the Parts from different Objects + bool impossible_multipart_selection = false; + if (type & itVolume && m_selection_mode == smVolume) + { + wxDataViewItemArray sels; + GetSelections(sels); + for (const auto& sel: sels) + if (sel != m_last_selected_item && + m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item)) + { + impossible_multipart_selection = true; + break; + } + } + + if (impossible_multipart_selection || + type & itSettings || type & itVolume && m_selection_mode == smInstance || !(type & itVolume) && m_selection_mode == smVolume) { - Unselect(m_last_selected_item); - // Inform user why selection isn't complited const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); - const wxString msg_str = wxString::Format(_(L("You started your selection with %s Item.")) + "\n" + - _(L("In this mode you can select only another %s Items%s")), item_type, item_type, - m_selection_mode == smInstance ? "." : " " + _(L("of a current Object"))); + msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" + + _(L("You started your selection with %s Item.")) + "\n" + + _(L("In this mode you can select only other %s Items%s")), + item_type, item_type, + m_selection_mode == smInstance ? "." : + " " + _(L("of a current Object"))); - wxMessageDialog dialog(this, _(L("Unsupported selection")) + "\n\n" + msg_str, - _(L("Info")), wxICON_INFORMATION); - dialog.ShowModal(); - - return false; + // Unselect last selected item, if selection is without SHIFT + if (!is_shift_pressed) { + Unselect(m_last_selected_item); + show_info(this, msg_str, _(L("Info"))); + } + + return is_shift_pressed; } return true; @@ -2237,7 +2275,8 @@ void ObjectList::fix_multiselection_conflicts() return; } - if (!check_last_selection()) + wxString msg_string; + if (!check_last_selection(msg_string)) return; m_prevent_list_events = true; @@ -2245,49 +2284,59 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); - m_selection_mode = smInstance; + if (m_selection_mode == smVolume) + { + // identify correct parent of the initial selected item + const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front()); - for (const auto item : sels) { - if (!IsSelected(item)) // if this item is unselected now (from previous actions) - continue; + sels.clear(); + wxDataViewItemArray children; // selected volumes from current parent + m_objects_model->GetChildren(parent, children); - if (m_objects_model->GetItemType(item) & itSettings) { - Unselect(item); - continue; - } + for (const auto child : children) + if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) + sels.Add(child); - const wxDataViewItem& parent = m_objects_model->GetParent(item); - if (parent != wxDataViewItem(0) && IsSelected(parent)) - Unselect(parent); - else + // If some part is selected, unselect all items except of selected parts of the current object + UnselectAll(); + SetSelections(sels); + } + else + { + for (const auto item : sels) { - wxDataViewItemArray unsels; - m_objects_model->GetAllChildren(item, unsels); - for (const auto unsel_item : unsels) - Unselect(unsel_item); - } + if (!IsSelected(item)) // if this item is unselected now (from previous actions) + continue; - if (m_objects_model->GetItemType(item) == itVolume) - { - // If some part is selected, unselect all items except of selected parts of the current object - sels.clear(); - - wxDataViewItemArray children; // selected volumes from current parent - m_objects_model->GetChildren(parent, children); - for (const auto child : children) - { - if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) - sels.Add(child); + if (m_objects_model->GetItemType(item) & itSettings) { + Unselect(item); + continue; } - UnselectAll(); - SetSelections(sels); + const wxDataViewItem& parent = m_objects_model->GetParent(item); + if (parent != wxDataViewItem(0) && IsSelected(parent)) + Unselect(parent); + else + { + wxDataViewItemArray unsels; + m_objects_model->GetAllChildren(item, unsels); + for (const auto unsel_item : unsels) + Unselect(unsel_item); + } - m_selection_mode = smVolume; - break; + if (m_objects_model->GetItemType(item) & itVolume) + Unselect(item); + + m_selection_mode = smInstance; } } + if (!msg_string.IsEmpty()) + show_info(this, msg_string, _(L("Info"))); + + if (!IsSelected(m_last_selected_item)) + m_last_selected_item = wxDataViewItem(0); + m_prevent_list_events = false; } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 0611cc4f1..43d5dc059 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -267,7 +267,7 @@ public: void select_all(); void select_item_all_children(); void update_selection_mode(); - bool check_last_selection(); + bool check_last_selection(wxString& msg_str); // correct current selections to avoid of the possible conflicts void fix_multiselection_conflicts(); From 763a91e2ca73a2a9189ea47bbdcb80089e3262b8 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 4 Apr 2019 15:13:43 +0200 Subject: [PATCH 13/23] Export to stl of parts and modifiers --- src/slic3r/GUI/GUI_ObjectList.cpp | 3 ++- src/slic3r/GUI/Plater.cpp | 21 +++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c725fae43..7148be513 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1025,7 +1025,7 @@ void ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) void ObjectList::append_menu_item_export_stl(wxMenu* menu) const { - append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, "", + append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "", [](wxCommandEvent&) { wxGetApp().plater()->export_stl(true); }, "", menu); menu->AppendSeparator(); } @@ -1101,6 +1101,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) #endif // __WXOSX__ append_menu_item_fix_through_netfabb(menu); + append_menu_item_export_stl(menu); m_menu_item_split_part = append_menu_item_split(menu); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 53d999283..247e4a0e8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2775,7 +2775,10 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ if (is_part) { item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); - } else { + + sidebar->obj_list()->append_menu_item_export_stl(menu); + } + else { wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")), [this](wxCommandEvent&) { q->increase_instances(); }, "add.png"); wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")), @@ -2808,8 +2811,9 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, _(L("Export this single object as STL file")), [this](wxCommandEvent&) { q->export_stl(true); }); + + menu->AppendSeparator(); } - menu->AppendSeparator(); sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); @@ -3317,8 +3321,17 @@ void Plater::export_stl(bool selection_only) const auto obj_idx = selection.get_object_idx(); if (obj_idx == -1) { return; } - mesh = p->model.objects[obj_idx]->mesh(); - } else { + + if (selection.get_mode() == Selection::Instance) + mesh = p->model.objects[obj_idx]->mesh(); + else + { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + mesh = p->model.objects[obj_idx]->volumes[volume->volume_idx()]->mesh; + mesh.transform(volume->get_volume_transformation().get_matrix()); + } + } + else { mesh = p->model.mesh(); } From b56991d780b342bcc7d9b4894be3ae98aa8ff0f0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 4 Apr 2019 16:03:23 +0200 Subject: [PATCH 14/23] Fix for leaving the object outside bed after "optimize orientation" --- src/slic3r/GUI/Plater.cpp | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 247e4a0e8..b9eea5bd4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -38,6 +38,8 @@ #include "libslic3r/SLA/SLARotfinder.hpp" #include "libslic3r/Utils.hpp" +#include "libnest2d/optimizers/nlopt/genetic.hpp" + #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" @@ -2100,7 +2102,9 @@ void Plater::priv::sla_optimize_rotation() { rotoptimizing.store(true); int obj_idx = get_selected_object_idx(); - ModelObject * o = model.objects[obj_idx]; + if(obj_idx < 0) { rotoptimizing.store(false); return; } + + ModelObject * o = model.objects[size_t(obj_idx)]; background_process.stop(); @@ -2108,7 +2112,7 @@ void Plater::priv::sla_optimize_rotation() { statusbar()->set_range(100); auto stfn = [this] (unsigned st, const std::string& msg) { - statusbar()->set_progress(st); + statusbar()->set_progress(int(st)); statusbar()->set_status_text(msg); // could be problematic, but we need the cancel button. @@ -2126,8 +2130,59 @@ void Plater::priv::sla_optimize_rotation() { [this](){ return !rotoptimizing.load(); } ); + const auto *bed_shape_opt = config->opt("bed_shape"); + assert(bed_shape_opt); + + auto& bedpoints = bed_shape_opt->values; + Polyline bed; bed.points.reserve(bedpoints.size()); + for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + + double mindist = 6.0; // FIXME + double offs = mindist / 2.0 - EPSILON; + if(rotoptimizing.load()) // wasn't canceled - for(ModelInstance * oi : o->instances) oi->set_rotation({r[X], r[Y], r[Z]}); + for(ModelInstance * oi : o->instances) { + oi->set_rotation({r[X], r[Y], r[Z]}); + + auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); + + namespace opt = libnest2d::opt; + opt::StopCriteria stopcr; + stopcr.relative_score_difference = 0.01; + stopcr.max_iterations = 10000; + stopcr.stop_score = 0.0; + opt::GeneticOptimizer solver(stopcr); + Polygon pbed(bed); + + auto bin = pbed.bounding_box(); + double binw = bin.size()(X) * SCALING_FACTOR - offs; + double binh = bin.size()(Y) * SCALING_FACTOR - offs; + + auto result = solver.optimize_min([&trchull, binw, binh](double rot){ + auto chull = trchull; + chull.rotate(rot); + + auto bb = chull.bounding_box(); + double bbw = bb.size()(X) * SCALING_FACTOR; + double bbh = bb.size()(Y) * SCALING_FACTOR; + + auto wdiff = bbw - binw; + auto hdiff = bbh - binh; + double diff = 0; + if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff; + if(wdiff > 0) diff += wdiff; + if(hdiff > 0) diff += hdiff; + + return diff; + }, opt::initvals(0.0), opt::bound(-PI/2, PI/2)); + + double r = std::get<0>(result.optimum); + + Vec3d rt = oi->get_rotation(); rt(Z) += r; + oi->set_rotation(rt); + } + + arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed); // Correct the z offset of the object which was corrupted be the rotation o->ensure_on_bed(); From ef1a273f0c31e6f606681a9b94be41c3d09d5feb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Apr 2019 16:16:57 +0200 Subject: [PATCH 15/23] Fix of "Latest commit of Slic3r 1.42.0 cuts away part of the model #2063" Fixed one bug of many in admesh library, where the face connectivity was broken when removing a degenerate face. Likely there are some similar issues left to be solved. Placed a workaround to always recalculate face connectivity before slicing. --- src/admesh/connect.cpp | 64 +++++++++++++++++------------------ src/admesh/stl.h | 2 -- src/admesh/stl_io.cpp | 18 ---------- src/libslic3r/PrintObject.cpp | 11 +++++- 4 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index 5fc992b63..fb3213219 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -39,8 +39,7 @@ static void stl_record_neighbors(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_initialize_facet_check_exact(stl_file *stl); static void stl_initialize_facet_check_nearby(stl_file *stl); -static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, - stl_vertex *a, stl_vertex *b); +static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b); static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance); static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, @@ -60,41 +59,40 @@ extern int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); static void stl_update_connects_remove_1(stl_file *stl, int facet_num); - -void -stl_check_facets_exact(stl_file *stl) { - /* This function builds the neighbors list. No modifications are made - * to any of the facets. The edges are said to match only if all six - * floats of the first edge matches all six floats of the second edge. - */ - - stl_hash_edge edge; - stl_facet facet; - int i; - int j; - - if (stl->error) return; +// This function builds the neighbors list. No modifications are made +// to any of the facets. The edges are said to match only if all six +// floats of the first edge matches all six floats of the second edge. +void stl_check_facets_exact(stl_file *stl) +{ + if (stl->error) + return; stl->stats.connected_edges = 0; stl->stats.connected_facets_1_edge = 0; stl->stats.connected_facets_2_edge = 0; stl->stats.connected_facets_3_edge = 0; - stl_initialize_facet_check_exact(stl); + // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. + // Do it before the next step, as the next step stores references to the face indices in the hash tables and removing a facet + // will break the references. + for (int i = 0; i < stl->stats.number_of_facets;) { + stl_facet &facet = stl->facet_start[i]; + if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) { + // Remove the degenerate facet. + facet = stl->facet_start[--stl->stats.number_of_facets]; + stl->stats.facets_removed += 1; + stl->stats.degenerate_facets += 1; + } else + ++ i; + } - for(i = 0; i < stl->stats.number_of_facets; i++) { - facet = stl->facet_start[i]; - // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. - if (facet.vertex[0] == facet.vertex[1] || - facet.vertex[1] == facet.vertex[2] || - facet.vertex[0] == facet.vertex[2]) { - stl->stats.degenerate_facets += 1; - stl_remove_facet(stl, i); - -- i; - continue; - } - for(j = 0; j < 3; j++) { - edge.facet_number = i; + // Connect neighbor edges. + stl_initialize_facet_check_exact(stl); + for (int i = 0; i < stl->stats.number_of_facets; i++) { + const stl_facet &facet = stl->facet_start[i]; + for (int j = 0; j < 3; j++) { + stl_hash_edge edge; + edge.facet_number = i; edge.which_edge = j; stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); insert_hash_edge(stl, edge, stl_record_neighbors); @@ -109,9 +107,7 @@ stl_check_facets_exact(stl_file *stl) { #endif } -static void -stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, - stl_vertex *a, stl_vertex *b) { +static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b) { if (stl->error) return; @@ -333,7 +329,9 @@ static void stl_free_edges(stl_file *stl) } } free(stl->heads); + stl->heads = nullptr; free(stl->tail); + stl->tail = nullptr; } static void stl_initialize_facet_check_nearby(stl_file *stl) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index afff3deac..2c436b426 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -127,7 +127,6 @@ typedef struct { typedef struct { FILE *fp; stl_facet *facet_start; - stl_edge *edge_start; stl_hash_edge **heads; stl_hash_edge *tail; int M; @@ -142,7 +141,6 @@ typedef struct { extern void stl_open(stl_file *stl, const char *file); extern void stl_close(stl_file *stl); extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file); -extern void stl_print_edges(stl_file *stl, FILE *file); extern void stl_print_neighbors(stl_file *stl, char *file); extern void stl_put_little_int(FILE *fp, int value_in); extern void stl_put_little_float(FILE *fp, float value_in); diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 71e434cbc..85f66785b 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -33,24 +33,6 @@ #define SEEK_END 2 #endif -void -stl_print_edges(stl_file *stl, FILE *file) { - int i; - int edges_allocated; - - if (stl->error) return; - - edges_allocated = stl->stats.number_of_facets * 3; - for(i = 0; i < edges_allocated; i++) { - fprintf(file, "%d, %f, %f, %f, %f, %f, %f\n", - stl->edge_start[i].facet_number, - stl->edge_start[i].p1(0), stl->edge_start[i].p1(1), - stl->edge_start[i].p1(2), stl->edge_start[i].p2(0), - stl->edge_start[i].p2(1), stl->edge_start[i].p2(2)); - } -} - - void stl_stats_out(stl_file *stl, FILE *file, char *input_file) { if (stl->error) return; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 42c6fbf75..954e583f7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1790,8 +1790,13 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, if (! volumes.empty()) { // Compose mesh. //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh(volumes.front()->mesh); + TriangleMesh mesh(volumes.front()->mesh); mesh.transform(volumes.front()->get_matrix(), true); + assert(mesh.repaired); + if (volumes.size() == 1 && mesh.repaired) { + //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. + stl_check_facets_exact(&mesh.stl); + } for (size_t idx_volume = 1; idx_volume < volumes.size(); ++ idx_volume) { const ModelVolume &model_volume = *volumes[idx_volume]; TriangleMesh vol_mesh(model_volume.mesh); @@ -1821,6 +1826,10 @@ std::vector PrintObject::_slice_volume(const std::vector &z, //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. TriangleMesh mesh(volume.mesh); mesh.transform(volume.get_matrix(), true); + if (mesh.repaired) { + //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. + stl_check_facets_exact(&mesh.stl); + } if (mesh.stl.stats.number_of_facets > 0) { mesh.transform(m_trafo, true); // apply XY shift From 8631cb006b73559728d307723912a14e9947280e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 29 Mar 2019 14:36:09 +0100 Subject: [PATCH 16/23] Improved Selection from ObjectList side --- src/slic3r/GUI/GUI_ObjectList.cpp | 102 ++++++++++++++++++---- src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 +- src/slic3r/GUI/Selection.hpp | 2 + src/slic3r/GUI/wxExtensions.cpp | 26 ++++++ src/slic3r/GUI/wxExtensions.hpp | 2 +- 6 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 7148be513..550337ab7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1639,16 +1639,20 @@ void ObjectList::part_selection_changed() bool update_and_show_manipulations = false; bool update_and_show_settings = false; - if (multiple_selection()) { + const auto item = GetSelection(); + + if ( multiple_selection() || item && m_objects_model->GetItemType(item) == itInstanceRoot ) + { og_name = _(L("Group manipulation")); - update_and_show_manipulations = true; + + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + // don't show manipulation panel for case of all Object's parts selection + update_and_show_manipulations = !selection.is_single_full_instance(); } else { - const auto item = GetSelection(); if (item) { - bool is_part = false; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { obj_idx = m_objects_model->GetIdByItem(item); og_name = _(L("Object manipulation")); @@ -1666,7 +1670,6 @@ void ObjectList::part_selection_changed() } else { og_name = _(L("Part Settings to modify")); - is_part = true; auto main_parent = m_objects_model->GetParent(parent); obj_idx = m_objects_model->GetIdByItem(main_parent); const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); @@ -1676,7 +1679,6 @@ void ObjectList::part_selection_changed() } else if (m_objects_model->GetItemType(item) == itVolume) { og_name = _(L("Part manipulation")); - is_part = true; const auto volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; @@ -1924,6 +1926,15 @@ bool ObjectList::multiple_selection() const return GetSelectedItemsCount() > 1; } +bool ObjectList::is_selected(const ItemType type) const +{ + const wxDataViewItem& item = GetSelection(); + if (item) + return m_objects_model->GetItemType(item) == type; + + return false; +} + void ObjectList::update_selections() { const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); @@ -1942,25 +1953,64 @@ void ObjectList::update_selections() return; } } - - if (selection.is_single_full_object()) +// if (selection.is_single_full_object() && selection.get_instance_idx() != -1) +// { +// sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); +// } +// else if (selection.is_single_volume() || selection.is_modifier() || +// selection.is_multiple_volume() || selection.is_multiple_full_object()) +// { +// for (auto idx : selection.get_volume_idxs()) { +// const auto gl_vol = selection.get_volume(idx); +// if (selection.is_multiple_full_object()) +// sels.Add(m_objects_model->GetItemById(gl_vol->object_idx())); +// else if (gl_vol->volume_idx() >= 0) +// // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids +// // are not associated with ModelVolumes, but they are temporarily generated by the backend +// // (for example, SLA supports or SLA pad). +// sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); +// } +// } + else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { - sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); + const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content(); + for (const auto& object : objects_content) { + if (object.second.size() == 1) // object with 1 instance + sels.Add(m_objects_model->GetItemById(object.first)); + else if (object.second.size() > 1) // object with several instances + { + wxDataViewItemArray current_sels; + GetSelections(current_sels); + const wxDataViewItem frst_inst_item = m_objects_model->GetItemByInstanceId(object.first, 0); + + bool root_is_selected = false; + for (const auto& item:current_sels) + if (item == m_objects_model->GetParent(frst_inst_item)) { + root_is_selected = true; + break; + } + if (root_is_selected) + continue; + + const Selection::InstanceIdxsList& instances = object.second; + for (const auto& inst : instances) + sels.Add(m_objects_model->GetItemByInstanceId(object.first, inst)); + } + } } - else if (selection.is_single_volume() || selection.is_modifier() || - selection.is_multiple_volume() || selection.is_multiple_full_object()) { + else if (selection.is_single_volume() || selection.is_modifier() || selection.is_multiple_volume()) + { for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); - if (selection.is_multiple_full_object()) - sels.Add(m_objects_model->GetItemById(gl_vol->object_idx())); - else if (gl_vol->volume_idx() >= 0) + if (gl_vol->volume_idx() >= 0) // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids // are not associated with ModelVolumes, but they are temporarily generated by the backend // (for example, SLA supports or SLA pad). sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); } } - else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) { + else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) + { for (auto idx : selection.get_instance_idxs()) { sels.Add(m_objects_model->GetItemByInstanceId(selection.get_object_idx(), idx)); } @@ -2137,11 +2187,25 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); - for (auto item : sels) { - if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) + for (const auto item : sels) { + if (!IsSelected(item)) // if this item is unselected now (from previous actions) + continue; + + if (m_objects_model->GetItemType(item) & itSettings) { Unselect(item); - else if (m_objects_model->GetParent(item) != wxDataViewItem(0)) - Unselect(m_objects_model->GetParent(item)); + continue; + } + + const wxDataViewItem& parent = m_objects_model->GetParent(item); + if (parent != wxDataViewItem(0) && IsSelected(parent)) + Unselect(parent); + else + { + wxDataViewItemArray unsels; + m_objects_model->GetAllChildren(item, unsels); + for (const auto unsel_item : unsels) + Unselect(unsel_item); + } } m_prevent_list_events = false; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 4ddc6ea71..b0ed756f8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -251,6 +251,7 @@ public: void init_objects(); bool multiple_selection() const ; + bool is_selected(const ItemType type) const; void update_selections(); void update_selections_on_canvas(); void select_item(const wxDataViewItem& item); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 61e98d542..48bb1f690 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -161,6 +161,8 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_move_label_string = L("Position"); m_new_rotate_label_string = L("Rotation"); m_new_scale_label_string = L("Scale factors"); + + ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one @@ -187,7 +189,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_enabled = true; } - else if (selection.is_single_full_object()) + else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) { m_cache.instance.reset(); @@ -212,7 +214,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_size = (volume->get_volume_transformation().get_matrix(true, true) * volume->bounding_box.size()).cwiseAbs(); m_new_enabled = true; } - else if (wxGetApp().obj_list()->multiple_selection()) + else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { reset_settings_value(); m_new_move_label_string = L("Translate"); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 4b2a72d7c..883fb5400 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -133,10 +133,12 @@ private: const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; } }; +public: typedef std::map VolumesCache; typedef std::set InstanceIdxsList; typedef std::map ObjectIdxsToInstanceIdxsMap; +private: struct Cache { // Cache of GLVolume derived transformation matrices, valid during mouse dragging. diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 09096cb05..0671bb62c 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1238,6 +1238,32 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, return count; } +void PrusaObjectDataViewModel::GetAllChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node) { + for (auto object : m_objects) + array.Add(wxDataViewItem((void*)object)); + } + else if (node->GetChildCount() == 0) + return; + else { + const size_t count = node->GetChildren().GetCount(); + for (size_t pos = 0; pos < count; pos++) { + PrusaObjectDataViewModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + } + + wxDataViewItemArray new_array = array; + for (const auto item : new_array) + { + wxDataViewItemArray children; + GetAllChildren(item, children); + WX_APPEND_ARRAY(array, children); + } +} + ItemType PrusaObjectDataViewModel::GetItemType(const wxDataViewItem &item) const { if (!item.IsOk()) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 27da67deb..72221962c 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -511,7 +511,7 @@ public: virtual bool IsContainer(const wxDataViewItem &item) const override; virtual unsigned int GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const override; - + void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const; // Is the container just a header or an item with all columns // In our case it is an item with all columns virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } From ccee04cd689017ba4880e6cd74d8c170ac40e7af Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 4 Apr 2019 18:01:53 +0200 Subject: [PATCH 17/23] Implemented new logic for Instances splitting --- src/slic3r/GUI/GUI_ObjectList.cpp | 41 ++++++++++++++++++++++++++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 1 + 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 550337ab7..c93096875 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1120,8 +1120,16 @@ void ObjectList::create_instance_popupmenu(wxMenu*menu) { m_menu_item_split_instances = append_menu_item_instance_to_object(menu); + /* New behavior logic: + * 1. Split Object to several separated object, if ALL instances are selected + * 2. Separate selected instances from the initial object to the separated object, + * if some (not all) instances are selected + */ wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { - evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId()); +// evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId()); + evt.SetText(wxGetApp().plater()->canvas3D()->get_selection().is_single_full_object() ? + _(L("Set as a Separated Objects")) : _(L("Set as a Separated Object"))); + }, m_menu_item_split_instances->GetId()); } wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) @@ -2358,6 +2366,31 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set } } +void ObjectList::instances_to_separated_objects(const int obj_idx) +{ + const int inst_cnt = (*m_objects)[obj_idx]->instances.size(); + + for (int i = inst_cnt-1; i > 0 ; i--) + { + // create new object from initial + ModelObject* object = (*m_objects)[obj_idx]->get_model()->add_object(*(*m_objects)[obj_idx]); + for (int inst_idx = object->instances.size() - 1; inst_idx >= 0; inst_idx--) + { + if (inst_idx == i) + continue; + // delete unnecessary instances + object->delete_instance(inst_idx); + } + + // Add new object to the object_list + add_object_to_list(m_objects->size() - 1); + + // delete current instance from the initial object + del_subobject_from_object(obj_idx, i, itInstance); + delete_instance_from_list(obj_idx, i); + } +} + void ObjectList::split_instances() { const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); @@ -2365,6 +2398,12 @@ void ObjectList::split_instances() if (obj_idx == -1) return; + if (selection.is_single_full_object()) + { + instances_to_separated_objects(obj_idx); + return; + } + const int inst_idx = selection.get_instance_idx(); const std::set inst_idxs = inst_idx < 0 ? selection.get_instance_idxs() : diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b0ed756f8..48d87db3c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -270,6 +270,7 @@ public: void update_object_menu(); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); + void instances_to_separated_objects(const int obj_idx); void split_instances(); void rename_item(); void fix_through_netfabb() const; From 77dcb7f5a39d5f99c3c6a50e225e7e1b4be1bc54 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 5 Apr 2019 09:04:52 +0200 Subject: [PATCH 18/23] Added missed code for my last commit --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c93096875..62cdb2d7c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2346,6 +2346,12 @@ void ObjectList::update_object_menu() void ObjectList::instances_to_separated_object(const int obj_idx, const std::set& inst_idxs) { + if ((*m_objects)[obj_idx]->instances.size() == inst_idxs.size()) + { + instances_to_separated_objects(obj_idx); + return; + } + // create new object from selected instance ModelObject* model_object = (*m_objects)[obj_idx]->get_model()->add_object(*(*m_objects)[obj_idx]); for (int inst_idx = model_object->instances.size() - 1; inst_idx >= 0; inst_idx--) From 177f5b02fa1472f25eb47c6925f20dfae983d7da Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 5 Apr 2019 09:51:58 +0200 Subject: [PATCH 19/23] Disabled layer editing mode for SLA --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 79a3684ef..cc1794216 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4382,7 +4382,7 @@ bool GLCanvas3D::_init_toolbar() item.sprite_id = 8; item.is_toggable = true; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; - item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; + item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; if (!m_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b9eea5bd4..c3352866a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3027,6 +3027,9 @@ bool Plater::priv::can_split() const bool Plater::priv::layers_height_allowed() const { + if (printer_technology != ptFFF) + return false; + int obj_idx = get_selected_object_idx(); return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); } From 88059baddba34fafd9952775491732192999fb3d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Apr 2019 10:01:48 +0200 Subject: [PATCH 20/23] Detect Remote Desktop connection and use Mesa OpenGL renderer. --- src/slic3r_app_msvc.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slic3r_app_msvc.cpp b/src/slic3r_app_msvc.cpp index 48c166406..380d30cf4 100644 --- a/src/slic3r_app_msvc.cpp +++ b/src/slic3r_app_msvc.cpp @@ -212,7 +212,12 @@ int wmain(int argc, wchar_t **argv) argv_extended.emplace_back(nullptr); OpenGLVersionCheck opengl_version_check; - bool load_mesa = ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); + bool load_mesa = + // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context. + // In that case, use Mesa. + ::GetSystemMetrics(SM_REMOTESESSION) || + // Try to load the default OpenGL driver and test its context version. + ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); From a9223aeb5f803736b0c94b2ebbb466ae79e69399 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 5 Apr 2019 10:08:34 +0200 Subject: [PATCH 21/23] Follow-up of 763a91e2ca73a2a9189ea47bbdcb80089e3262b8 -> take in account of ModelObject::origin_translation when saving parts and modifiers to stl --- src/libslic3r/TriangleMesh.cpp | 5 +++++ src/libslic3r/TriangleMesh.hpp | 1 + src/slic3r/GUI/Plater.cpp | 9 +++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 0d9a79978..bfba364af 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -273,6 +273,11 @@ void TriangleMesh::translate(float x, float y, float z) stl_invalidate_shared_vertices(&this->stl); } +void TriangleMesh::translate(const Vec3f &displacement) +{ + translate(displacement(0), displacement(1), displacement(2)); +} + void TriangleMesh::rotate(float angle, const Axis &axis) { if (angle == 0.f) diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 527846f9d..d389500c6 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -40,6 +40,7 @@ public: void scale(float factor); void scale(const Vec3d &versor); void translate(float x, float y, float z); + void translate(const Vec3f &displacement); void rotate(float angle, const Axis &axis); void rotate(float angle, const Vec3d& axis); void rotate_x(float angle) { this->rotate(angle, X); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c3352866a..ec2e88f84 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3380,18 +3380,19 @@ void Plater::export_stl(bool selection_only) const auto obj_idx = selection.get_object_idx(); if (obj_idx == -1) { return; } + const ModelObject* model_object = p->model.objects[obj_idx]; if (selection.get_mode() == Selection::Instance) - mesh = p->model.objects[obj_idx]->mesh(); + mesh = model_object->mesh(); else { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - mesh = p->model.objects[obj_idx]->volumes[volume->volume_idx()]->mesh; + mesh = model_object->volumes[volume->volume_idx()]->mesh; mesh.transform(volume->get_volume_transformation().get_matrix()); + mesh.translate(-model_object->origin_translation.cast()); } } - else { + else mesh = p->model.mesh(); - } Slic3r::store_stl(path_u8.c_str(), &mesh, true); p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path)); From ef5281c3c989a3022ee32f390f8bf76d0202adf4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 5 Apr 2019 11:18:13 +0200 Subject: [PATCH 22/23] Some improvements for a loading of icons --- src/slic3r/GUI/BitmapCache.cpp | 28 +++++++++++++++++++++------- src/slic3r/GUI/BitmapCache.hpp | 8 ++++---- src/slic3r/GUI/Tab.cpp | 3 ++- src/slic3r/GUI/wxExtensions.cpp | 19 ++++++++++++------- src/slic3r/GUI/wxExtensions.hpp | 2 +- 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 4c7f999ff..bea242470 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -178,9 +178,11 @@ wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned i return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); } -wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned int height) +wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned int width, unsigned int height) { - std::string bitmap_key = bitmap_name + "-h" + std::to_string(height); + std::string bitmap_key = bitmap_name + ( height !=0 ? + "-h" + std::to_string(height) : + "-w" + std::to_string(width)); auto it = m_map.find(bitmap_key); if (it != m_map.end()) return it->second; @@ -189,14 +191,23 @@ wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned int hei if (! image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) || image.GetWidth() == 0 || image.GetHeight() == 0) return nullptr; - if (image.GetHeight() != height) - image.Rescale(int(0.5f + float(image.GetWidth()) * height / image.GetHeight()), height, wxIMAGE_QUALITY_BILINEAR); + + if (height != 0 && image.GetHeight() != height) + width = int(0.5f + float(image.GetWidth()) * height / image.GetHeight()); + else if (width != 0 && image.GetWidth() != width) + height = int(0.5f + float(image.GetHeight()) * width / image.GetWidth()); + + if (height != 0 && width != 0) + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); } -wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned int target_height) +wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned int target_width, unsigned int target_height) { - std::string bitmap_key = bitmap_name + "-h" + std::to_string(target_height); + std::string bitmap_key = bitmap_name + (target_height != 0 ? + "-h" + std::to_string(target_height) : + "-w" + std::to_string(target_width)); auto it = m_map.find(bitmap_key); if (it != m_map.end()) return it->second; @@ -205,7 +216,10 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned int tar if (image == nullptr) return nullptr; - float scale = (float)target_height / image->height; + float scale = target_height != 0 ? + (float)target_height / image->height : target_width != 0 ? + (float)target_width / image->width : 1; + int width = (int)(scale * image->width + 0.5f); int height = (int)(scale * image->height + 0.5f); int n_pixels = width * height; diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index ce5eb3c77..8915783a3 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -31,10 +31,10 @@ public: wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end); wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned int width, unsigned int height, const unsigned char *raw_data); - // Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height if nonzero. - wxBitmap* load_png(const std::string &bitmap_key, unsigned int height = 0); - // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height. - wxBitmap* load_svg(const std::string &bitmap_key, unsigned int height); + // Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero. + wxBitmap* load_png(const std::string &bitmap_key, unsigned int width = 0, unsigned int height = 0); + // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width. + wxBitmap* load_svg(const std::string &bitmap_key, unsigned int width = 0, unsigned int height = 0); static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency); static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a91dae026..c2cdf8f03 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1638,7 +1638,8 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) } auto printhost_browse = [=](wxWindow* parent) { - auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse ")) + dots, + wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetBitmap(create_scaled_bitmap("zoom.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 0671bb62c..7755bc30a 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -423,15 +423,20 @@ void PrusaCollapsiblePaneMSW::Collapse(bool collapse) // PrusaObjectDataViewModelNode // ---------------------------------------------------------------------------- -wxBitmap create_scaled_bitmap(const std::string& bmp_name_in) +// If an icon has horizontal orientation (width > height) call this function with is_horizontal = true +wxBitmap create_scaled_bitmap(const std::string& bmp_name_in, const bool is_horizontal /* = false*/) { static Slic3r::GUI::BitmapCache cache; - const auto height = (unsigned int)(Slic3r::GUI::wxGetApp().em_unit() * 1.6f + 0.5f); - std::string bmp_name = bmp_name_in; + + unsigned int height, width = height = 0; + unsigned int& scale_base = is_horizontal ? width : height; + scale_base = (unsigned int)(Slic3r::GUI::wxGetApp().em_unit() * 1.6f + 0.5f); + + std::string bmp_name = bmp_name_in; boost::replace_last(bmp_name, ".png", ""); - wxBitmap *bmp = cache.load_svg(bmp_name, height); + wxBitmap *bmp = cache.load_svg(bmp_name, height, width); if (bmp == nullptr) - bmp = cache.load_png(bmp_name, height); + bmp = cache.load_png(bmp_name, height, width); return *bmp; } @@ -1481,8 +1486,8 @@ PrusaDoubleSlider::PrusaDoubleSlider(wxWindow *parent, if (!is_osx) SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX - m_bmp_thumb_higher = wxBitmap(create_scaled_bitmap(style == wxSL_HORIZONTAL ? "right_half_circle.png" : "up_half_circle.png")); - m_bmp_thumb_lower = wxBitmap(create_scaled_bitmap(style == wxSL_HORIZONTAL ? "left_half_circle.png" : "down_half_circle.png")); + m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? create_scaled_bitmap("right_half_circle.png") : create_scaled_bitmap("up_half_circle.png", true)); + m_bmp_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? create_scaled_bitmap("left_half_circle.png" ) : create_scaled_bitmap("down_half_circle.png", true)); m_thumb_size = m_bmp_thumb_lower.GetSize(); m_bmp_add_tick_on = create_scaled_bitmap("colorchange_add_on.png"); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 72221962c..912475ced 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -31,7 +31,7 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, wxEvtHandler* event_handler); -wxBitmap create_scaled_bitmap(const std::string& bmp_name); +wxBitmap create_scaled_bitmap(const std::string& bmp_name, const bool is_horizontal = false); class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup { From 233eda28131081a052a401744ee4e94143972726 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 5 Apr 2019 11:30:49 +0200 Subject: [PATCH 23/23] Export to stl of instances --- src/slic3r/GUI/Plater.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ec2e88f84..3e7ab3bbc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2864,7 +2864,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")), [this](wxCommandEvent&) { reload_from_disk(); }); - append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, _(L("Export this single object as STL file")), + append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")), [this](wxCommandEvent&) { q->export_stl(true); }); menu->AppendSeparator(); @@ -3382,7 +3382,12 @@ void Plater::export_stl(bool selection_only) const ModelObject* model_object = p->model.objects[obj_idx]; if (selection.get_mode() == Selection::Instance) - mesh = model_object->mesh(); + { + if (selection.is_single_full_object()) + mesh = model_object->mesh(); + else + mesh = model_object->full_raw_mesh(); + } else { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());