#include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_App.hpp" #include "I18N.hpp" #include "OptionsGroup.hpp" #include "PresetBundle.hpp" #include "Tab.hpp" #include "wxExtensions.hpp" #include "libslic3r/Model.hpp" #include "LambdaObjectDialog.hpp" #include "GLCanvas3D.hpp" #include "Selection.hpp" #include #include "slic3r/Utils/FixModelByWin10.hpp" namespace Slic3r { namespace GUI { wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); // pt_FFF FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF = { { L("Layers and Perimeters"), { "layer_height" , "perimeters", "top_solid_layers", "bottom_solid_layers" } }, { L("Infill") , { "fill_density", "fill_pattern" } }, { L("Support material") , { "support_material", "support_material_auto", "support_material_threshold", "support_material_pattern", "support_material_buildplate_only", "support_material_spacing" } }, { L("Extruders") , { "wipe_into_infill", "wipe_into_objects" } } }; // pt_SLA FreqSettingsBundle FREQ_SETTINGS_BUNDLE_SLA = { { L("Pad and Support") , { "supports_enable", "pad_enable" } } }; static PrinterTechnology printer_technology() { return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); } // Config from current edited printer preset static DynamicPrintConfig& printer_config() { return wxGetApp().preset_bundle->printers.get_edited_preset().config; } static int extruders_count() { return wxGetApp().extruders_cnt(); } ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), m_parent(parent) { // Fill CATEGORY_ICON { // Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget, // see note in PresetBundle::load_compatible_bitmaps() // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill"); CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); // CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); // ptSLA CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); } // create control create_objects_ctrl(); init_icons(); // describe control behavior 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(); #else // To avoid selection update from SetSelection() and UnselectAll() under osx if (m_prevent_list_events) return; #endif // __APPLE__ /* 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()); #endif //__WXMSW__ }); Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX #ifdef __WXMSW__ GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { set_tooltip_for_item(/*event.GetPosition()*/get_mouse_position_in_control()); event.Skip(); }); #endif //__WXMSW__ Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &ObjectList::OnContextMenu, this); Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, &ObjectList::OnBeginDrag, this); Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, &ObjectList::OnDropPossible, this); Bind(wxEVT_DATAVIEW_ITEM_DROP, &ObjectList::OnDrop, this); Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); }); #ifdef __WXOSX__ Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); #endif //__WXOSX__ Bind(wxEVT_SIZE, ([this](wxSizeEvent &e) { this->EnsureVisible(this->GetCurrentItem()); e.Skip(); })); } ObjectList::~ObjectList() { } void ObjectList::create_objects_ctrl() { /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: * 1. set a height of the list to some big value * 2. change it to the normal min value (15 * wxGetApp().em_unit()) after first whole Mainframe updating/layouting */ SetMinSize(wxSize(-1, 3000)); m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(this, 1, wxGROW); m_objects_model = new ObjectDataViewModel; AssociateModel(m_objects_model); m_objects_model->SetAssociatedControl(this); #if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE EnableDragSource(wxDF_UNICODETEXT); EnableDropTarget(wxDF_UNICODETEXT); #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE // column 0(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(), 0, 20*wxGetApp().em_unit()/*200*/, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); // column 1 of the view control: AppendColumn(create_objects_list_extruder_column(4)); // column 2 of the view control: AppendBitmapColumn(" ", 2, wxDATAVIEW_CELL_INERT, int(2.5 * wxGetApp().em_unit())/*25*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); } void ObjectList::create_popup_menus() { // create popup menus for object and part create_object_popupmenu(&m_menu_object); create_part_popupmenu(&m_menu_part); create_sla_object_popupmenu(&m_menu_sla_object); create_instance_popupmenu(&m_menu_instance); } void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(0)*/) { const wxDataViewItem item = input_item == wxDataViewItem(0) ? GetSelection() : input_item; if (!item) { obj_idx = vol_idx = -1; return; } const ItemType type = m_objects_model->GetItemType(item); obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; } int ObjectList::get_mesh_errors_count(const int obj_idx, const int vol_idx /*= -1*/) const { if (obj_idx < 0) return 0; return (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx); } wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /*= -1*/) const { const int errors = get_mesh_errors_count(obj_idx, vol_idx); if (errors == 0) return ""; // hide tooltip // Create tooltip string, if there are errors wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); const stl_stats& stats = vol_idx == -1 ? (*m_objects)[obj_idx]->get_object_stl_stats() : (*m_objects)[obj_idx]->volumes[vol_idx]->mesh.stl.stats; std::map error_msg = { { L("degenerate facets"), stats.degenerate_facets }, { L("edges fixed"), stats.edges_fixed }, { L("facets removed"), stats.facets_removed }, { L("facets added"), stats.facets_added }, { L("facets reversed"), stats.facets_reversed }, { L("backwards edges"), stats.backwards_edges } }; for (const auto& error : error_msg) if (error.second > 0) tooltip += wxString::Format("\t%d %s\n", error.second, _(error.first)); if (is_windows10()) tooltip += _(L("Right button click the icon to fix STL through Netfabb")); return tooltip; } wxString ObjectList::get_mesh_errors_list() { if (!GetSelection()) return ""; int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx); return get_mesh_errors_list(obj_idx, vol_idx); } void ObjectList::set_tooltip_for_item(const wxPoint& pt) { wxDataViewItem item; wxDataViewColumn* col; HitTest(pt, item, col); if (!item) return; /* GetMainWindow() return window, associated with wxDataViewCtrl. * And for this window we should to set tooltips. * Just this->SetToolTip(tooltip) => has no effect. */ if (col->GetTitle() == " " && GetSelectedItemsCount()<2) GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); else if (col->GetTitle() == _("Name")) { #ifdef __WXMSW__ if (pt.x < 2 * wxGetApp().em_unit() || pt.x > 4 * wxGetApp().em_unit()) { GetMainWindow()->SetToolTip(""); // hide tooltip return; } #endif //__WXMSW__ int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx, item); GetMainWindow()->SetToolTip(get_mesh_errors_list(obj_idx, vol_idx)); } else GetMainWindow()->SetToolTip(""); // hide tooltip } wxPoint ObjectList::get_mouse_position_in_control() { const wxPoint& pt = wxGetMousePosition(); // wxWindow* win = GetMainWindow(); // wxPoint screen_pos = win->GetScreenPosition(); return wxPoint(pt.x - /*win->*/GetScreenPosition().x, pt.y - /*win->*/GetScreenPosition().y); } int ObjectList::get_selected_obj_idx() const { if (GetSelectedItemsCount() == 1) return m_objects_model->GetIdByItem(m_objects_model->GetTopParent(GetSelection())); return -1; } DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) const { assert(item); const ItemType type = m_objects_model->GetItemType(item); const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); return type & itObject|itInstance ? (*m_objects)[obj_idx]->config : (*m_objects)[obj_idx]->volumes[vol_idx]->config; } wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_count) { wxArrayString choices; choices.Add("default"); for (int i = 1; i <= extruders_count; ++i) choices.Add(wxString::Format("%d", i)); wxDataViewChoiceRenderer *c = new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 1, 8*wxGetApp().em_unit()/*80*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); return column; } void ObjectList::update_extruder_values_for_items(const int max_extruder) { for (int i = 0; i < m_objects->size(); ++i) { wxDataViewItem item = m_objects_model->GetItemById(i); if (!item) continue; auto object = (*m_objects)[i]; wxString extruder; if (!object->config.has("extruder") || object->config.option("extruder")->value > max_extruder) extruder = "default"; else extruder = wxString::Format("%d", object->config.option("extruder")->value); m_objects_model->SetValue(extruder, item, 1); if (object->volumes.size() > 1) { for (auto id = 0; id < object->volumes.size(); id++) { item = m_objects_model->GetItemByVolumeId(i, id); if (!item) continue; if (!object->volumes[id]->config.has("extruder") || object->volumes[id]->config.option("extruder")->value > max_extruder) extruder = "default"; else extruder = wxString::Format("%d", object->volumes[id]->config.option("extruder")->value); m_objects_model->SetValue(extruder, item, 1); } } } } void ObjectList::update_objects_list_extruder_column(int extruders_count) { if (!this) return; // #ys_FIXME if (printer_technology() == ptSLA) extruders_count = 1; wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(1)->GetRenderer()); if (ch_render->GetChoices().GetCount() - 1 == extruders_count) return; m_prevent_update_extruder_in_config = true; if (m_objects && extruders_count > 1) update_extruder_values_for_items(extruders_count); // delete old 2nd column DeleteColumn(GetColumn(1)); // insert new created 3rd column InsertColumn(1, create_objects_list_extruder_column(extruders_count)); // set show/hide for this column set_extruder_column_hidden(extruders_count <= 1); //a workaround for a wrong last column width updating under OSX GetColumn(2)->SetWidth(25); m_prevent_update_extruder_in_config = false; } void ObjectList::set_extruder_column_hidden(const bool hide) const { GetColumn(1)->SetHidden(hide); } void ObjectList::update_extruder_in_config(const wxDataViewItem& item) { if (m_prevent_update_extruder_in_config) return; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { const int obj_idx = m_objects_model->GetIdByItem(item); m_config = &(*m_objects)[obj_idx]->config; } else { const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); const int volume_id = m_objects_model->GetVolumeIdByItem(item); if (obj_idx < 0 || volume_id < 0) return; m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } wxVariant variant; m_objects_model->GetValue(variant, item, 1); const wxString selection = variant.GetString(); if (!m_config || selection.empty()) return; const int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); // update scene wxGetApp().plater()->update(); } void ObjectList::update_name_in_model(const wxDataViewItem& item) const { const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (obj_idx < 0) return; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data(); return; } const int volume_id = m_objects_model->GetVolumeIdByItem(item); if (volume_id < 0) return; (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); } void ObjectList::init_icons() { m_bmp_solidmesh = ScalableBitmap(nullptr, "add_part"); // Add part m_bmp_modifiermesh = ScalableBitmap(nullptr, "add_modifier"); // Add modifier m_bmp_support_enforcer = ScalableBitmap(nullptr, "support_enforcer");// Add support enforcer m_bmp_support_blocker = ScalableBitmap(nullptr, "support_blocker"); // Add support blocker m_bmp_vector.reserve(4); // bitmaps for different types of parts m_bmp_vector.push_back(&m_bmp_solidmesh.bmp()); m_bmp_vector.push_back(&m_bmp_modifiermesh.bmp()); m_bmp_vector.push_back(&m_bmp_support_enforcer.bmp()); m_bmp_vector.push_back(&m_bmp_support_blocker.bmp()); // Set volumes default bitmaps for the model m_objects_model->SetVolumeBitmaps(m_bmp_vector); // init icon for manifold warning m_bmp_manifold_warning = ScalableBitmap(nullptr, "exclamation"); // Set warning bitmap for the model m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); // init bitmap for "Split to sub-objects" context menu m_bmp_split = ScalableBitmap(nullptr, "split_parts_SMALL"); // init bitmap for "Add Settings" context menu m_bmp_cog = ScalableBitmap(nullptr, "cog"); } void ObjectList::rescale_icons() { m_bmp_vector.clear(); m_bmp_vector.reserve(4); // bitmaps for different types of parts for (ScalableBitmap* bitmap : std::vector { &m_bmp_solidmesh, // Add part &m_bmp_modifiermesh, // Add modifier &m_bmp_support_enforcer, // Add support enforcer &m_bmp_support_blocker }) // Add support blocker { bitmap->msw_rescale(); m_bmp_vector.push_back(& bitmap->bmp()); } // Set volumes default bitmaps for the model m_objects_model->SetVolumeBitmaps(m_bmp_vector); m_bmp_manifold_warning.msw_rescale(); // Set warning bitmap for the model m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); m_bmp_split.msw_rescale(); m_bmp_cog.msw_rescale(); // Update CATEGORY_ICON according to new scale { // Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget, // see note in PresetBundle::load_compatible_bitmaps() // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill"); CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); // CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); // ptSLA CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); } } void ObjectList::selection_changed() { if (m_prevent_list_events) return; fix_multiselection_conflicts(); // update object selection on Plater if (!m_prevent_canvas_selection_update) update_selections_on_canvas(); // to update the toolbar and info sizer if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { auto event = SimpleEvent(EVT_OBJ_LIST_OBJECT_SELECT); event.SetEventObject(this); wxPostEvent(this, event); } part_selection_changed(); } void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) { if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) return; if (volumes.empty()) return; ModelObject& model_object = *(*m_objects)[obj_idx]; const auto object_item = m_objects_model->GetItemById(obj_idx); wxDataViewItemArray items; for (const ModelVolume* volume : volumes) { const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(), volume->get_mesh_errors_count()>0 , volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); auto opt_keys = volume->config.keys(); if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) select_item(m_objects_model->AddSettingsChild(vol_item)); items.Add(vol_item); } changed_object(obj_idx); if (items.size() > 1) { m_selection_mode = smVolume; m_last_selected_item = wxDataViewItem(0); } select_items(items); #ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); #endif //no __WXOSX__ //__WXMSW__ } void ObjectList::paste_objects_into_list(const std::vector& object_idxs) { if (object_idxs.empty()) return; wxDataViewItemArray items; for (const size_t object : object_idxs) { add_object_to_list(object); items.Add(m_objects_model->GetItemById(object)); } wxGetApp().plater()->changed_objects(object_idxs); select_items(items); #ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); #endif //no __WXOSX__ //__WXMSW__ } void ObjectList::OnChar(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_BACK){ remove(); } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT)) select_item_all_children(); event.Skip(); } void ObjectList::OnContextMenu(wxDataViewEvent&) { wxDataViewItem item; wxDataViewColumn* col; const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); if (!item) #ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX // after Yosemite OS X version, HitTest return undefined item item = GetSelection(); if (item) show_context_menu(); else printf("undefined item\n"); return; #else return; #endif // __WXOSX__ const wxString title = col->GetTitle(); if (title == " ") show_context_menu(); else if (title == _("Name")) { int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx, item); if (is_windows10() && get_mesh_errors_count(obj_idx, vol_idx) > 0 && pt.x > 2*wxGetApp().em_unit() && pt.x < 4*wxGetApp().em_unit() ) fix_through_netfabb(); } #ifndef __WXMSW__ GetMainWindow()->SetToolTip(""); // hide tooltip #endif //__WXMSW__ } void ObjectList::show_context_menu() { if (multiple_selection()) { if (selected_instances_of_same_object()) wxGetApp().plater()->PopupMenu(&m_menu_instance); else show_multi_selection_menu(); return; } const auto item = GetSelection(); if (item) { const ItemType type = m_objects_model->GetItemType(item); if (!(type & (itObject | itVolume | itInstance))) return; wxMenu* menu = type & itInstance ? &m_menu_instance : m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; if (!(type & itInstance)) append_menu_item_settings(menu); wxGetApp().plater()->PopupMenu(menu); } } void ObjectList::key_event(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_TAB) Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); else if (event.GetKeyCode() == WXK_DELETE #ifdef __WXOSX__ || event.GetKeyCode() == WXK_BACK #endif //__WXOSX__ ) { remove(); } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) select_item_all_children(); else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); else event.Skip(); } void ObjectList::OnBeginDrag(wxDataViewEvent &event) { const wxDataViewItem item(event.GetItem()); const bool mult_sel = multiple_selection(); if (mult_sel && !selected_instances_of_same_object() || !mult_sel && (GetSelection() != item || m_objects_model->GetParent(item) == wxDataViewItem(0) ) ) { event.Veto(); return; } const ItemType& type = m_objects_model->GetItemType(item); if (!(type & (itVolume | itInstance))) { event.Veto(); return; } if (mult_sel) { m_dragged_data.init(m_objects_model->GetObjectIdByItem(item),type); std::set& sub_obj_idxs = m_dragged_data.inst_idxs(); wxDataViewItemArray sels; GetSelections(sels); for (auto sel : sels ) sub_obj_idxs.insert(m_objects_model->GetInstanceIdByItem(sel)); } else m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), type&itVolume ? m_objects_model->GetVolumeIdByItem(item) : m_objects_model->GetInstanceIdByItem(item), type); /* Under MSW or OSX, DnD moves an item to the place of another selected item * But under GTK, DnD moves an item between another two items. * And as a result - call EVT_CHANGE_SELECTION to unselect all items. * To prevent such behavior use m_prevent_list_events **/ m_prevent_list_events = true;//it's needed for GTK /* Under GTK, DnD requires to the wxTextDataObject been initialized with some valid value, * so set some nonempty string */ wxTextDataObject* obj = new wxTextDataObject; obj->SetText("Some text");//it's needed for GTK event.SetDataObject(obj); event.SetDragFlags(wxDrag_DefaultMove); // allows both copy and move; } bool ObjectList::can_drop(const wxDataViewItem& item) const { return m_dragged_data.type() == itInstance && !item.IsOk() || m_dragged_data.type() == itVolume && item.IsOk() && m_objects_model->GetItemType(item) == itVolume && m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item); } void ObjectList::OnDropPossible(wxDataViewEvent &event) { const wxDataViewItem& item = event.GetItem(); if (!can_drop(item)) event.Veto(); } void ObjectList::OnDrop(wxDataViewEvent &event) { const wxDataViewItem& item = event.GetItem(); if (!can_drop(item)) { event.Veto(); m_dragged_data.clear(); return; } if (m_dragged_data.type() == itInstance) { instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); m_dragged_data.clear(); return; } const int from_volume_id = m_dragged_data.sub_obj_idx(); int to_volume_id = m_objects_model->GetVolumeIdByItem(item); // It looks like a fixed in current version of the wxWidgets // #ifdef __WXGTK__ // /* Under GTK, DnD moves an item between another two items. // * And event.GetItem() return item, which is under "insertion line" // * So, if we move item down we should to decrease the to_volume_id value // **/ // if (to_volume_id > from_volume_id) to_volume_id--; // #endif // __WXGTK__ auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; auto delta = to_volume_id < from_volume_id ? -1 : 1; int cnt = 0; for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) std::swap(volumes[id], volumes[id + delta]); select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, m_objects_model->GetParent(item))); changed_object(m_dragged_data.obj_idx()); m_dragged_data.clear(); } // Context Menu std::vector ObjectList::get_options(const bool is_part) { if (printer_technology() == ptSLA) { SLAPrintObjectConfig full_sla_config; auto options = full_sla_config.keys(); options.erase(find(options.begin(), options.end(), "layer_height")); return options; } PrintRegionConfig reg_config; auto options = reg_config.keys(); if (!is_part) { PrintObjectConfig obj_config; std::vector obj_options = obj_config.keys(); options.insert(options.end(), obj_options.begin(), obj_options.end()); } return options; } const std::vector& ObjectList::get_options_for_bundle(const wxString& bundle_name) { const FreqSettingsBundle& bundle = printer_technology() == ptSLA ? FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF; for (auto& it : bundle) { if (bundle_name == _(it.first)) return it.second; } #if 0 // if "Quick menu" is selected FreqSettingsBundle& bundle_quick = printer_technology() == ptSLA ? m_freq_settings_sla: m_freq_settings_fff; for (auto& it : bundle_quick) { if ( bundle_name == wxString::Format(_(L("Quick Add Settings (%s)")), _(it.first)) ) return it.second; } #endif static std::vector empty; return empty; } void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part) { auto options = get_options(is_part); const int extruders_cnt = extruders_count(); DynamicPrintConfig config; for (auto& option : options) { auto const opt = config.def()->get(option); auto category = opt->category; if (category.empty() || (category == "Extruders" && extruders_cnt == 1)) continue; const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label; std::pair option_label(option, label); std::vector< std::pair > new_category; auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); cat_opt_label.push_back(option_label); if (cat_opt_label.size() == 1) settings_menu[category] = cat_opt_label; } } void ObjectList::get_settings_choice(const wxString& category_name) { wxArrayString names; wxArrayInt selections; settings_menu_hierarchy settings_menu; const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0); get_options_menu(settings_menu, is_part); std::vector< std::pair > *settings_list = nullptr; auto opt_keys = m_config->keys(); for (auto& cat : settings_menu) { if (_(cat.first) == category_name) { int sel = 0; for (auto& pair : cat.second) { names.Add(_(pair.second)); if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) selections.Add(sel); sel++; } settings_list = &cat.second; break; } } if (!settings_list) return; if (wxGetSelectedChoices(selections, _(L("Select showing settings")), category_name, names) == -1) return; const int selection_cnt = selections.size(); #if 0 if (selection_cnt > 0) { // Add selected items to the "Quick menu" FreqSettingsBundle& freq_settings = printer_technology() == ptSLA ? m_freq_settings_sla : m_freq_settings_fff; bool changed_existing = false; std::vector tmp_freq_cat = {}; for (auto& cat : freq_settings) { if (_(cat.first) == category_name) { std::vector& freq_settings_category = cat.second; freq_settings_category.clear(); freq_settings_category.reserve(selection_cnt); for (auto sel : selections) freq_settings_category.push_back((*settings_list)[sel].first); changed_existing = true; break; } } if (!changed_existing) { // Create new "Quick menu" item for (auto& cat : settings_menu) { if (_(cat.first) == category_name) { freq_settings[cat.first] = std::vector {}; std::vector& freq_settings_category = freq_settings.find(cat.first)->second; freq_settings_category.reserve(selection_cnt); for (auto sel : selections) freq_settings_category.push_back((*settings_list)[sel].first); break; } } } } #endif std::vector selected_options; selected_options.reserve(selection_cnt); for (auto sel : selections) selected_options.push_back((*settings_list)[sel].first); const DynamicPrintConfig& from_config = printer_technology() == ptFFF ? wxGetApp().preset_bundle->prints.get_edited_preset().config : wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; for (auto& setting : (*settings_list)) { auto& opt_key = setting.first; if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && find(selected_options.begin(), selected_options.end(), opt_key) == selected_options.end()) m_config->erase(opt_key); if (find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && find(selected_options.begin(), selected_options.end(), opt_key) != selected_options.end()) { const ConfigOption* option = from_config.option(opt_key); if (!option) { // if current option doesn't exist in prints.get_edited_preset(), // get it from default config values option = DynamicPrintConfig::new_from_defaults_keys({ opt_key })->option(opt_key); } m_config->set_key_value(opt_key, option->clone()); } } // Add settings item for object update_settings_item(); } void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { const std::vector& options = get_options_for_bundle(bundle_name); /* #ys_FIXME_delete_after_testing ! Temporary workaround to avoid a crash: * After a right click on an instance all items in the ObjectList are unselected, * and as a result m_config == nullptr. */ if (!m_config) return; auto opt_keys = m_config->keys(); const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (auto& opt_key : options) { if (find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end()) { const ConfigOption* option = from_config.option(opt_key); if (!option) { // if current option doesn't exist in prints.get_edited_preset(), // get it from default config values option = DynamicPrintConfig::new_from_defaults_keys({ opt_key })->option(opt_key); } m_config->set_key_value(opt_key, option->clone()); } } // Add settings item for object update_settings_item(); } void ObjectList::update_settings_item() { auto item = GetSelection(); if (item) { if (m_objects_model->GetItemType(item) == itInstance) item = m_objects_model->GetTopParent(item); const auto settings_item = m_objects_model->IsSettingsItem(item) ? item : m_objects_model->GetSettingsItem(item); select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); // update object selection on Plater if (!m_prevent_canvas_selection_update) update_selections_on_canvas(); } else { auto panel = wxGetApp().sidebar().scrolled_panel(); panel->Freeze(); wxGetApp().obj_settings()->UpdateAndShow(true); panel->Thaw(); } } void ObjectList::append_menu_item_add_generic(wxMenuItem* menu, const ModelVolumeType type) { auto sub_menu = new wxMenu; if (wxGetApp().get_mode() == comExpert) { append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "", [this, type](wxCommandEvent&) { load_subobject(type); }, "", menu->GetMenu()); sub_menu->AppendSeparator(); } for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) { append_menu_item(sub_menu, wxID_ANY, _(item), "", [this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu->GetMenu()); } menu->SetSubMenu(sub_menu); } void ObjectList::append_menu_items_add_volume(wxMenu* menu) { // Note: id accords to type of the sub-object, so sequence of the menu items is important std::vector menu_object_types_items = {L("Add part"), // ~ModelVolumeType::MODEL_PART L("Add modifier"), // ~ModelVolumeType::PARAMETER_MODIFIER L("Add support enforcer"), // ~ModelVolumeType::SUPPORT_ENFORCER L("Add support blocker") }; // ~ModelVolumeType::SUPPORT_BLOCKER // Update "add" items(delete old & create new) settings popupmenu for (auto& item : menu_object_types_items){ const auto settings_id = menu->FindItem(_(item)); if (settings_id != wxNOT_FOUND) menu->Destroy(settings_id); } const ConfigOptionMode mode = wxGetApp().get_mode(); if (mode == comAdvanced) { append_menu_item(menu, wxID_ANY, _(L("Add part")), "", [this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); }, *m_bmp_vector[int(ModelVolumeType::MODEL_PART)]); } if (mode == comSimple) { append_menu_item(menu, wxID_ANY, _(L("Add support enforcer")), "", [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); }, *m_bmp_vector[int(ModelVolumeType::SUPPORT_ENFORCER)]); append_menu_item(menu, wxID_ANY, _(L("Add support blocker")), "", [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); }, *m_bmp_vector[int(ModelVolumeType::SUPPORT_BLOCKER)]); return; } for (int type = mode == comExpert ? 0 : 1 ; type < menu_object_types_items.size(); type++) { auto& item = menu_object_types_items[type]; auto menu_item = new wxMenuItem(menu, wxID_ANY, _(item)); menu_item->SetBitmap(*m_bmp_vector[type]); append_menu_item_add_generic(menu_item, ModelVolumeType(type)); menu->Append(menu_item); } } wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) { return append_menu_item(menu, wxID_ANY, _(L("Split to parts")), "", [this](wxCommandEvent&) { split(); }, m_bmp_split.bmp(), menu); } wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) { MenuWithSeparators* menu = dynamic_cast(menu_); const wxString menu_name = _(L("Add settings")); // Delete old items from settings popupmenu auto settings_id = menu->FindItem(menu_name); if (settings_id != wxNOT_FOUND) menu->Destroy(settings_id); for (auto& it : FREQ_SETTINGS_BUNDLE_FFF) { settings_id = menu->FindItem(_(it.first)); if (settings_id != wxNOT_FOUND) menu->Destroy(settings_id); } for (auto& it : FREQ_SETTINGS_BUNDLE_SLA) { settings_id = menu->FindItem(_(it.first)); if (settings_id != wxNOT_FOUND) menu->Destroy(settings_id); } #if 0 for (auto& it : m_freq_settings_fff) { settings_id = menu->FindItem(wxString::Format(_(L("Quick Add Settings (%s)")), _(it.first))); if (settings_id != wxNOT_FOUND) menu->Destroy(settings_id); } for (auto& it : m_freq_settings_sla) { settings_id = menu->FindItem(wxString::Format(_(L("Quick Add Settings (%s)")), _(it.first))); if (settings_id != wxNOT_FOUND) menu->Destroy(settings_id); } #endif menu->DestroySeparators(); // delete old separators const auto sel_vol = get_selected_model_volume(); if (sel_vol && sel_vol->type() >= ModelVolumeType::SUPPORT_ENFORCER) return nullptr; const ConfigOptionMode mode = wxGetApp().get_mode(); if (mode == comSimple) return nullptr; // Create new items for settings popupmenu if (printer_technology() == ptFFF || menu->GetMenuItems().size() > 0 && !menu->GetMenuItems().back()->IsSeparator()) menu->SetFirstSeparator(); // Add frequently settings create_freq_settings_popupmenu(menu); if (mode == comAdvanced) return nullptr; menu->SetSecondSeparator(); // Add full settings list auto menu_item = new wxMenuItem(menu, wxID_ANY, menu_name); menu_item->SetBitmap(m_bmp_cog.bmp()); menu_item->SetSubMenu(create_settings_popupmenu(menu)); return menu->Append(menu_item); } wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu) { return append_menu_item(menu, wxID_ANY, _(L("Change type")), "", [this](wxCommandEvent&) { change_part_type(); }, "", menu); } wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu) { return append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "", [this](wxCommandEvent&) { split_instances(); }, "", menu); } void ObjectList::append_menu_items_osx(wxMenu* menu) { append_menu_item_delete(menu); append_menu_item(menu, wxID_ANY, _(L("Rename")), "", [this](wxCommandEvent&) { rename_item(); }, "", menu); menu->AppendSeparator(); } wxMenuItem* ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) { if (!is_windows10()) return nullptr; wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Fix through the Netfabb")), "", [this](wxCommandEvent&) { fix_through_netfabb(); }, "", menu); menu->AppendSeparator(); return menu_item; } void ObjectList::append_menu_item_export_stl(wxMenu* menu) const { append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "", [](wxCommandEvent&) { wxGetApp().plater()->export_stl(false, true); }, "", menu); menu->AppendSeparator(); } void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const { const wxString name = _(L("Change extruder")); // Delete old menu item const int item_id = menu->FindItem(name); if (item_id != wxNOT_FOUND) menu->Destroy(item_id); const int extruders_cnt = extruders_count(); const wxDataViewItem item = GetSelection(); if (item && extruders_cnt > 1) { DynamicPrintConfig& config = get_item_config(item); const int initial_extruder = !config.has("extruder") ? 0 : config.option("extruder")->value; wxMenu* extruder_selection_menu = new wxMenu(); for (int i = 0; i <= extruders_cnt; i++) { const wxString& item_name = i == 0 ? _(L("Default")) : wxString::Format("%d", i); append_menu_radio_item(extruder_selection_menu, wxID_ANY, item_name, "", [this, i](wxCommandEvent&) { set_extruder_for_selected_items(i); }, menu)->Check(i == initial_extruder); } menu->AppendSubMenu(extruder_selection_menu, name, _(L("Select new extruder for the object/part"))); } } 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__ append_menu_items_osx(menu); #endif // __WXOSX__ append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); // Split object to parts m_menu_item_split = append_menu_item_split(menu); menu->AppendSeparator(); // rest of a object_menu will be added later in: // - append_menu_items_add_volume() -> for "Add (volumes)" // - append_menu_item_settings() -> for "Add (settings)" wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(is_splittable()); }, m_menu_item_split->GetId()); } void ObjectList::create_sla_object_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ append_menu_items_osx(menu); #endif // __WXOSX__ append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); // rest of a object_sla_menu will be added later in: // - append_menu_item_settings() -> for "Add (settings)" } void ObjectList::create_part_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ append_menu_items_osx(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); // Append change part type menu->AppendSeparator(); append_menu_item_change_type(menu); // rest of a object_sla_menu will be added later in: // - append_menu_item_settings() -> for "Add (settings)" wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(is_splittable()); }, m_menu_item_split_part->GetId()); } 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); /* 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.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) { wxMenu *menu = new wxMenu; settings_menu_hierarchy settings_menu; const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0); get_options_menu(settings_menu, is_part); for (auto cat : settings_menu) { append_menu_item(menu, wxID_ANY, _(cat.first), "", [menu, this](wxCommandEvent& event) { get_settings_choice(menu->GetLabel(event.GetId())); }, CATEGORY_ICON.find(cat.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(cat.first), parent_menu); } return menu; } void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) { // Add default settings bundles const FreqSettingsBundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; const int extruders_cnt = extruders_count(); for (auto& it : bundle) { if (it.first.empty() || it.first == "Extruders" && extruders_cnt == 1) continue; append_menu_item(menu, wxID_ANY, _(it.first), "", [menu, this](wxCommandEvent& event) { get_freq_settings_choice(menu->GetLabel(event.GetId())); }, CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu); } #if 0 // Add "Quick" settings bundles const FreqSettingsBundle& bundle_quick = printer_technology() == ptFFF ? m_freq_settings_fff : m_freq_settings_sla; for (auto& it : bundle_quick) { if (it.first.empty() || it.first == "Extruders" && extruders_cnt == 1) continue; append_menu_item(menu, wxID_ANY, wxString::Format(_(L("Quick Add Settings (%s)")), _(it.first)), "", [menu, this](wxCommandEvent& event) { get_freq_settings_choice(menu->GetLabel(event.GetId())); }, CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu); } #endif } void ObjectList::update_opt_keys(t_config_option_keys& opt_keys) { auto full_current_opts = get_options(false); for (int i = opt_keys.size()-1; i >= 0; --i) if (find(full_current_opts.begin(), full_current_opts.end(), opt_keys[i]) == full_current_opts.end()) opt_keys.erase(opt_keys.begin() + i); } void ObjectList::load_subobject(ModelVolumeType type) { auto item = GetSelection(); if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) return; int obj_idx = m_objects_model->GetIdByItem(item); if (obj_idx < 0) return; std::vector> volumes_info; load_part((*m_objects)[obj_idx], volumes_info, type); changed_object(obj_idx); wxDataViewItem sel_item; for (const auto& volume : volumes_info ) sel_item = m_objects_model->AddVolumeChild(item, volume.first, type, volume.second); if (sel_item) select_item(sel_item); } void ObjectList::load_part( ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type) { wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); wxArrayString input_files; wxGetApp().import_model(parent, input_files); for (int i = 0; i < input_files.size(); ++i) { std::string input_file = input_files.Item(i).ToUTF8().data(); Model model; try { model = Model::read_from_file(input_file); } catch (std::exception &e) { auto msg = _(L("Error! ")) + input_file + " : " + e.what() + "."; show_error(parent, msg); exit(1); } for (auto object : model.objects) { Vec3d delta = Vec3d::Zero(); if (model_object->origin_translation != Vec3d::Zero()) { object->center_around_origin(); delta = model_object->origin_translation - object->origin_translation; } for (auto volume : object->volumes) { #if !ENABLE_VOLUMES_CENTERING_FIXES volume->center_geometry(); #endif // !ENABLE_VOLUMES_CENTERING_FIXES volume->translate(delta); auto new_volume = model_object->add_volume(*volume); new_volume->set_type(type); new_volume->name = boost::filesystem::path(input_file).filename().string(); volumes_info.push_back(std::make_pair(from_u8(new_volume->name), new_volume->get_mesh_errors_count()>0)); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); } } } } // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity // as possible in least squares norm in regard to the 8 corners of bbox. // Bounding box is expected to be centered around zero in all axes. Geometry::Transformation volume_to_bed_transformation(const Geometry::Transformation &instance_transformation, const BoundingBoxf3 &bbox) { Geometry::Transformation out; // Is the angle close to a multiple of 90 degrees? auto ninety_degrees = [](double a) { a = fmod(std::abs(a), 0.5 * PI); if (a > 0.25 * PI) a = 0.5 * PI - a; return a < 0.001; }; if (instance_transformation.is_scaling_uniform()) { // No need to run the non-linear least squares fitting for uniform scaling. // Just set the inverse. out.set_from_transform(instance_transformation.get_matrix(true).inverse()); } else if (ninety_degrees(instance_transformation.get_rotation().x()) && ninety_degrees(instance_transformation.get_rotation().y()) && ninety_degrees(instance_transformation.get_rotation().z())) { // Anisotropic scaling, rotation by multiples of ninety degrees. Eigen::Matrix3d instance_rotation_trafo = (Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(instance_transformation.get_rotation().y(), Vec3d::UnitY()) * Eigen::AngleAxisd(instance_transformation.get_rotation().x(), Vec3d::UnitX())).toRotationMatrix(); Eigen::Matrix3d volume_rotation_trafo = (Eigen::AngleAxisd(-instance_transformation.get_rotation().x(), Vec3d::UnitX()) * Eigen::AngleAxisd(-instance_transformation.get_rotation().y(), Vec3d::UnitY()) * Eigen::AngleAxisd(-instance_transformation.get_rotation().z(), Vec3d::UnitZ())).toRotationMatrix(); // 8 corners of the bounding box. auto pts = Eigen::MatrixXd(8, 3); pts(0, 0) = bbox.min.x(); pts(0, 1) = bbox.min.y(); pts(0, 2) = bbox.min.z(); pts(1, 0) = bbox.min.x(); pts(1, 1) = bbox.min.y(); pts(1, 2) = bbox.max.z(); pts(2, 0) = bbox.min.x(); pts(2, 1) = bbox.max.y(); pts(2, 2) = bbox.min.z(); pts(3, 0) = bbox.min.x(); pts(3, 1) = bbox.max.y(); pts(3, 2) = bbox.max.z(); pts(4, 0) = bbox.max.x(); pts(4, 1) = bbox.min.y(); pts(4, 2) = bbox.min.z(); pts(5, 0) = bbox.max.x(); pts(5, 1) = bbox.min.y(); pts(5, 2) = bbox.max.z(); pts(6, 0) = bbox.max.x(); pts(6, 1) = bbox.max.y(); pts(6, 2) = bbox.min.z(); pts(7, 0) = bbox.max.x(); pts(7, 1) = bbox.max.y(); pts(7, 2) = bbox.max.z(); // Corners of the bounding box transformed into the modifier mesh coordinate space, with inverse rotation applied to the modifier. auto qs = pts * (instance_rotation_trafo * Eigen::Scaling(instance_transformation.get_scaling_factor().cwiseProduct(instance_transformation.get_mirror())) * volume_rotation_trafo).inverse().transpose(); // Fill in scaling based on least squares fitting of the bounding box corners. Vec3d scale; for (int i = 0; i < 3; ++ i) scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i)); out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo)); out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2)))); out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1)); } else { // General anisotropic scaling, general rotation. // Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world. // Scale it to get the required size. out.set_scaling_factor(instance_transformation.get_scaling_factor().cwiseInverse()); } return out; } void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type) { const auto obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); assert(obj_idx == selection.get_object_idx()); /** Any changes of the Object's composition is duplicated for all Object's Instances * So, It's enough to take a bounding box of a first selected Instance and calculate Part(generic_subobject) position */ int instance_idx = *selection.get_instance_idxs().begin(); assert(instance_idx != -1); if (instance_idx == -1) return; // Selected object ModelObject &model_object = *(*m_objects)[obj_idx]; // Bounding box of the selected instance in world coordinate system including the translation, without modifiers. BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx); const wxString name = _(L("Generic")) + "-" + _(type_name); TriangleMesh mesh; double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1); if (type_name == "Box") // Sitting on the print bed, left front front corner at (0, 0). mesh = make_cube(side, side, side); else if (type_name == "Cylinder") // Centered around 0, sitting on the print bed. // The cylinder has the same volume as the box above. mesh = make_cylinder(0.564 * side, side); else if (type_name == "Sphere") // Centered around 0, half the sphere below the print bed, half above. // The sphere has the same volume as the box above. mesh = make_sphere(0.62 * side, PI / 18); else if (type_name == "Slab") // Sitting on the print bed, left front front corner at (0, 0). mesh = make_cube(instance_bb.size().x()*1.5, instance_bb.size().y()*1.5, instance_bb.size().z()*0.5); mesh.repair(); // Mesh will be centered when loading. ModelVolume *new_volume = model_object.add_volume(std::move(mesh)); new_volume->set_type(type); #if !ENABLE_GENERIC_SUBPARTS_PLACEMENT new_volume->set_offset(Vec3d(0.0, 0.0, model_object.origin_translation(2) - mesh.stl.stats.min(2))); #endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT #if !ENABLE_VOLUMES_CENTERING_FIXES new_volume->center_geometry(); #endif // !ENABLE_VOLUMES_CENTERING_FIXES #if ENABLE_GENERIC_SUBPARTS_PLACEMENT if (instance_idx != -1) { // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh.bounding_box(); new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); // Set the modifier position. auto offset = (type_name == "Slab") ? // Slab: Lift to print bed Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) : // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. Vec3d(instance_bb.max(0), instance_bb.min(1), instance_bb.min(2)) + 0.5 * mesh_bb.size() - v->get_instance_offset(); new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset); } #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT new_volume->name = into_u8(name); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); changed_object(obj_idx); const auto object_item = m_objects_model->GetTopParent(GetSelection()); select_item(m_objects_model->AddVolumeChild(object_item, name, type, new_volume->get_mesh_errors_count()>0)); #ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); #endif //no __WXOSX__ //__WXMSW__ } void ObjectList::del_object(const int obj_idx) { wxGetApp().plater()->delete_object_from_model(obj_idx); } // Delete subobject void ObjectList::del_subobject_item(wxDataViewItem& item) { if (!item) return; int obj_idx, idx; ItemType type; m_objects_model->GetItemInfo(item, type, obj_idx, idx); if (type == itUndef) return; if (type == itSettings) del_settings_from_config(); else if (type == itInstanceRoot && obj_idx != -1) del_instances_from_object(obj_idx); else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) return; // If last volume item with warning was deleted, unmark object item if (type == itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); m_objects_model->Delete(item); } void ObjectList::del_settings_from_config() { auto opt_keys = m_config->keys(); if (opt_keys.size() == 1 && opt_keys[0] == "extruder") return; int extruder = -1; if (m_config->has("extruder")) extruder = m_config->option("extruder")->value; m_config->clear(); if (extruder >= 0) m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); } void ObjectList::del_instances_from_object(const int obj_idx) { auto& instances = (*m_objects)[obj_idx]->instances; if (instances.size() <= 1) return; while ( instances.size()> 1) instances.pop_back(); (*m_objects)[obj_idx]->invalidate_bounding_box(); // ? #ys_FIXME changed_object(obj_idx); } bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { if (obj_idx == 1000) // Cannot delete a wipe tower. return false; ModelObject* object = (*m_objects)[obj_idx]; if (type == itVolume) { const auto volume = object->volumes[idx]; // if user is deleting the last solid part, throw error int solid_cnt = 0; for (auto vol : object->volumes) if (vol->is_model_part()) ++solid_cnt; if (volume->is_model_part() && solid_cnt == 1) { Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from object."))); return false; } object->delete_volume(idx); if (object->volumes.size() == 1) { const auto last_volume = object->volumes[0]; if (!last_volume->config.empty()) { object->config.apply(last_volume->config); last_volume->config.clear(); } } } else if (type == itInstance) { if (object->instances.size() == 1) { Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object."))); return false; } object->delete_instance(idx); } else return false; changed_object(obj_idx); return true; } void ObjectList::split() { const auto item = GetSelection(); const int obj_idx = get_selected_obj_idx(); if (!item || obj_idx < 0) return; ModelVolume* volume; if (!get_volume_by_item(item, volume)) return; DynamicPrintConfig& config = printer_config(); const ConfigOption *nozzle_dmtrs_opt = config.option("nozzle_diameter", false); const auto nozzle_dmrs_cnt = (nozzle_dmtrs_opt == nullptr) ? size_t(1) : dynamic_cast(nozzle_dmtrs_opt)->values.size(); if (volume->split(nozzle_dmrs_cnt) == 1) { wxMessageBox(_(L("The selected object couldn't be split because it contains only one part."))); return; } wxBusyCursor wait; auto model_object = (*m_objects)[obj_idx]; auto parent = m_objects_model->GetTopParent(item); if (parent) m_objects_model->DeleteVolumeChildren(parent); else parent = item; for (const ModelVolume* volume : model_object->volumes) { const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name), volume->is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, volume->get_mesh_errors_count()>0, volume->config.has("extruder") ? volume->config.option("extruder")->value : 0, false); // add settings to the part, if it has those auto opt_keys = volume->config.keys(); if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { select_item(m_objects_model->AddSettingsChild(vol_item)); Expand(vol_item); } } if (parent == item) Expand(parent); changed_object(obj_idx); } bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) { auto obj_idx = get_selected_obj_idx(); if (!item || obj_idx < 0) return false; const auto volume_id = m_objects_model->GetVolumeIdByItem(item); const bool split_part = m_objects_model->GetItemType(item) == itVolume; // object is selected if (volume_id < 0) { if ( split_part || (*m_objects)[obj_idx]->volumes.size() > 1 ) return false; volume = (*m_objects)[obj_idx]->volumes[0]; } // volume is selected else volume = (*m_objects)[obj_idx]->volumes[volume_id]; return true; } bool ObjectList::is_splittable() { const wxDataViewItem item = GetSelection(); if (!item) return false; ModelVolume* volume; if (!get_volume_by_item(item, volume) || !volume) return false; return volume->is_splittable(); } bool ObjectList::selected_instances_of_same_object() { wxDataViewItemArray sels; GetSelections(sels); const int obj_idx = m_objects_model->GetObjectIdByItem(sels.front()); for (auto item : sels) { if (! (m_objects_model->GetItemType(item) & itInstance) || obj_idx != m_objects_model->GetObjectIdByItem(item)) return false; } return true; } bool ObjectList::can_split_instances() { const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); return selection.is_multiple_full_instance() || selection.is_single_full_instance(); } // NO_PARAMETERS function call means that changed object index will be determine from Selection() void ObjectList::changed_object(const int obj_idx/* = -1*/) const { wxGetApp().plater()->changed_object(obj_idx < 0 ? get_selected_obj_idx() : obj_idx); } void ObjectList::part_selection_changed() { int obj_idx = -1; int volume_id = -1; m_config = nullptr; wxString og_name = wxEmptyString; bool update_and_show_manipulations = false; bool update_and_show_settings = false; const auto item = GetSelection(); if ( multiple_selection() || item && m_objects_model->GetItemType(item) == itInstanceRoot ) { og_name = _(L("Group manipulation")); 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 { if (item) { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { obj_idx = m_objects_model->GetIdByItem(item); og_name = _(L("Object manipulation")); m_config = &(*m_objects)[obj_idx]->config; update_and_show_manipulations = true; } else { auto parent = m_objects_model->GetParent(item); // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene obj_idx = m_objects_model->GetIdByItem(parent); if (m_objects_model->GetItemType(item) == itSettings) { if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { og_name = _(L("Object Settings to modify")); m_config = &(*m_objects)[obj_idx]->config; } else { og_name = _(L("Part Settings to modify")); 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); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } update_and_show_settings = true; } else if (m_objects_model->GetItemType(item) == itVolume) { og_name = _(L("Part manipulation")); volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; } else if (m_objects_model->GetItemType(item) == itInstance) { og_name = _(L("Instance manipulation")); update_and_show_manipulations = true; // fill m_config by object's values const int obj_idx_ = m_objects_model->GetObjectIdByItem(item); m_config = &(*m_objects)[obj_idx_]->config; } } } } m_selected_object_id = obj_idx; if (update_and_show_manipulations) { wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " "); if (item) { wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item)); wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_list(obj_idx, volume_id)); } } if (update_and_show_settings) wxGetApp().obj_settings()->get_og()->set_name(" " + og_name + " "); Sidebar& panel = wxGetApp().sidebar(); panel.Freeze(); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); wxGetApp().sidebar().show_info_sizer(); panel.Layout(); panel.Thaw(); } void ObjectList::add_object_to_list(size_t obj_idx) { auto model_object = (*m_objects)[obj_idx]; const wxString& item_name = from_u8(model_object->name); const auto item = m_objects_model->Add(item_name, !model_object->config.has("extruder") ? 0 : model_object->config.option("extruder")->value, get_mesh_errors_count(obj_idx) > 0); // add volumes to the object if (model_object->volumes.size() > 1) { for (const ModelVolume* volume : model_object->volumes) { const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, from_u8(volume->name), volume->type(), volume->get_mesh_errors_count()>0, !volume->config.has("extruder") ? 0 : volume->config.option("extruder")->value, false); auto opt_keys = volume->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(vol_item)); Expand(vol_item); } } Expand(item); } // add instances to the object, if it has those if (model_object->instances.size()>1) increase_object_instances(obj_idx, model_object->instances.size()); // add settings to the object, if it has those 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)); Expand(item); } #ifndef __WXOSX__ selection_changed(); #endif //__WXMSW__ } void ObjectList::delete_object_from_list() { auto item = GetSelection(); if (!item) return; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) select_item(m_objects_model->Delete(item)); else select_item(m_objects_model->Delete(m_objects_model->GetParent(item))); } void ObjectList::delete_object_from_list(const size_t obj_idx) { select_item(m_objects_model->Delete(m_objects_model->GetItemById(obj_idx))); } void ObjectList::delete_volume_from_list(const size_t obj_idx, const size_t vol_idx) { select_item(m_objects_model->Delete(m_objects_model->GetItemByVolumeId(obj_idx, vol_idx))); } void ObjectList::delete_instance_from_list(const size_t obj_idx, const size_t inst_idx) { select_item(m_objects_model->Delete(m_objects_model->GetItemByInstanceId(obj_idx, inst_idx))); } void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx) { if ( !(type&(itObject|itVolume|itInstance)) ) return; if (type&itObject) { del_object(obj_idx); delete_object_from_list(obj_idx); } else { del_subobject_from_object(obj_idx, sub_obj_idx, type); type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) : delete_instance_from_list(obj_idx, sub_obj_idx); } } void ObjectList::delete_from_model_and_list(const std::vector& items_for_delete) { if (items_for_delete.empty()) return; for (std::vector::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item) { if (!(item->type&(itObject | itVolume | itInstance))) continue; if (item->type&itObject) { del_object(item->obj_idx); m_objects_model->Delete(m_objects_model->GetItemById(item->obj_idx)); } else { if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type)) continue; if (item->type&itVolume) { m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx)); if ((*m_objects)[item->obj_idx]->volumes.size() == 1 && (*m_objects)[item->obj_idx]->config.has("extruder")) { const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option("extruder")->value); m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), 1); } wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx); } else m_objects_model->Delete(m_objects_model->GetItemByInstanceId(item->obj_idx, item->sub_obj_idx)); } } part_selection_changed(); } void ObjectList::delete_all_objects_from_list() { m_objects_model->DeleteAll(); part_selection_changed(); } void ObjectList::increase_object_instances(const size_t obj_idx, const size_t num) { select_item(m_objects_model->AddInstanceChild(m_objects_model->GetItemById(obj_idx), num)); } void ObjectList::decrease_object_instances(const size_t obj_idx, const size_t num) { select_item(m_objects_model->DeleteLastInstance(m_objects_model->GetItemById(obj_idx), num)); } void ObjectList::unselect_objects() { if (!GetSelection()) return; m_prevent_list_events = true; UnselectAll(); part_selection_changed(); m_prevent_list_events = false; } void ObjectList::select_current_object(int idx) { m_prevent_list_events = true; UnselectAll(); if (idx >= 0) Select(m_objects_model->GetItemById(idx)); part_selection_changed(); m_prevent_list_events = false; } void ObjectList::select_current_volume(int idx, int vol_idx) { if (vol_idx < 0) { select_current_object(idx); return; } m_prevent_list_events = true; UnselectAll(); if (idx >= 0) Select(m_objects_model->GetItemByVolumeId(idx, vol_idx)); part_selection_changed(); m_prevent_list_events = false; } void ObjectList::remove() { if (GetSelectedItemsCount() == 0) return; wxDataViewItemArray sels; GetSelections(sels); for (auto& item : sels) { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); else { if (sels.size() == 1) select_item(m_objects_model->GetParent(item)); del_subobject_item(item); } } } void ObjectList::init_objects() { m_objects = wxGetApp().model_objects(); } 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(); 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 ) { const auto item = GetSelection(); if (selection.is_single_full_object() && m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) return; if (selection.is_single_volume() || selection.is_any_modifier()) { const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) return; } } else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { 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) || item == m_objects_model->GetTopParent(frst_inst_item)) { root_is_selected = true; sels.Add(item); 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_any_volume() || selection.is_any_modifier()) { for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); 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())); } m_selection_mode = smVolume; } 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)); } } else if (selection.is_mixed()) { const Selection::ObjectIdxsToInstanceIdxsMap& objects_content_list = selection.get_content(); for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); const auto& glv_obj_idx = gl_vol->object_idx(); const auto& glv_ins_idx = gl_vol->instance_idx(); bool is_selected = false; for (auto obj_ins : objects_content_list) { if (obj_ins.first == glv_obj_idx) { if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { if (glv_ins_idx == 0 && (*m_objects)[glv_obj_idx]->instances.size() == 1) sels.Add(m_objects_model->GetItemById(glv_obj_idx)); else sels.Add(m_objects_model->GetItemByInstanceId(glv_obj_idx, glv_ins_idx)); is_selected = true; break; } } } if (is_selected) continue; const auto& glv_vol_idx = gl_vol->volume_idx(); if (glv_vol_idx == 0 && (*m_objects)[glv_obj_idx]->volumes.size() == 1) sels.Add(m_objects_model->GetItemById(glv_obj_idx)); else sels.Add(m_objects_model->GetItemByVolumeId(glv_obj_idx, glv_vol_idx)); } } if (sels.size() == 0) m_selection_mode = smUndef; select_items(sels); // Scroll selected Item in the middle of an object list this->EnsureVisible(this->GetCurrentItem()); } void ObjectList::update_selections_on_canvas() { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); const int sel_cnt = GetSelectedItemsCount(); if (sel_cnt == 0) { selection.clear(); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); return; } auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool 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 (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 (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, instance_idx, true); else add_to_selection(item, selection, instance_idx, true); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); wxGetApp().plater()->canvas3D()->render(); return; } wxDataViewItemArray sels; GetSelections(sels); selection.clear(); for (auto item: sels) add_to_selection(item, selection, instance_idx, false); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); wxGetApp().plater()->canvas3D()->render(); } void ObjectList::select_item(const wxDataViewItem& item) { if (! item.IsOk()) { return; } m_prevent_list_events = true; UnselectAll(); Select(item); part_selection_changed(); m_prevent_list_events = false; } void ObjectList::select_items(const wxDataViewItemArray& sels) { m_prevent_list_events = true; UnselectAll(); SetSelections(sels); part_selection_changed(); m_prevent_list_events = false; } void ObjectList::select_all() { SelectAll(); selection_changed(); } void ObjectList::select_item_all_children() { wxDataViewItemArray sels; // There is no selection before OR some object is selected => select all objects 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)) 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() { // All items are unselected if (!GetSelection()) { m_last_selected_item = wxDataViewItem(0); 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(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, show information about it */ const ItemType type = m_objects_model->GetItemType(m_last_selected_item); // 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) { // Inform user why selection isn't complited const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); 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"))); // 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; } void ObjectList::fix_multiselection_conflicts() { if (GetSelectedItemsCount() <= 1) { update_selection_mode(); return; } wxString msg_string; if (!check_last_selection(msg_string)) return; m_prevent_list_events = true; wxDataViewItemArray sels; GetSelections(sels); 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()); 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 some part is selected, unselect all items except of selected parts of the current object UnselectAll(); SetSelections(sels); } else { 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); 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); } 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; } ModelVolume* ObjectList::get_selected_model_volume() { auto item = GetSelection(); if (!item || m_objects_model->GetItemType(item) != itVolume) return nullptr; const auto vol_idx = m_objects_model->GetVolumeIdByItem(item); const auto obj_idx = get_selected_obj_idx(); if (vol_idx < 0 || obj_idx < 0) return nullptr; return (*m_objects)[obj_idx]->volumes[vol_idx]; } void ObjectList::change_part_type() { ModelVolume* volume = get_selected_model_volume(); if (!volume) return; const ModelVolumeType type = volume->type(); if (type == ModelVolumeType::MODEL_PART) { const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; int model_part_cnt = 0; for (auto vol : (*m_objects)[obj_idx]->volumes) { if (vol->type() == ModelVolumeType::MODEL_PART) ++model_part_cnt; } if (model_part_cnt == 1) { Slic3r::GUI::show_error(nullptr, _(L("You can't change a type of the last solid part of the object."))); return; } } const wxString names[] = { "Part", "Modifier", "Support Enforcer", "Support Blocker" }; auto new_type = ModelVolumeType(wxGetSingleChoiceIndex("Type: ", _(L("Select type of part")), wxArrayString(4, names), int(type))); if (new_type == type || new_type == ModelVolumeType::INVALID) return; const auto item = GetSelection(); volume->set_type(new_type); m_objects_model->SetVolumeType(item, new_type); changed_object(get_selected_obj_idx()); // Update settings showing, if we have it //(we show additional settings for Part and Modifier and hide it for Support Blocker/Enforcer) const auto settings_item = m_objects_model->GetSettingsItem(item); if (settings_item && (new_type == ModelVolumeType::SUPPORT_ENFORCER || new_type == ModelVolumeType::SUPPORT_BLOCKER)) { m_objects_model->Delete(settings_item); } else if (!settings_item && (new_type == ModelVolumeType::MODEL_PART || new_type == ModelVolumeType::PARAMETER_MODIFIER)) { select_item(m_objects_model->AddSettingsChild(item)); } } void ObjectList::last_volume_is_deleted(const int obj_idx) { if (obj_idx < 0 || m_objects->empty() || obj_idx <= m_objects->size() || (*m_objects)[obj_idx]->volumes.empty()) return; auto volume = (*m_objects)[obj_idx]->volumes[0]; // clear volume's config values volume->config.clear(); // set a default extruder value, since user can't add it manually volume->config.set_key_value("extruder", new ConfigOptionInt(0)); } bool ObjectList::has_multi_part_objects() { if (!m_objects_model->IsEmpty()) { wxDataViewItemArray items; m_objects_model->GetChildren(wxDataViewItem(0), items); for (auto& item : items) if (m_objects_model->GetItemByType(item, itVolume)) return true; } return false; } void ObjectList::update_settings_items() { m_prevent_canvas_selection_update = true; wxDataViewItemArray sel; GetSelections(sel); // stash selection wxDataViewItemArray items; m_objects_model->GetChildren(wxDataViewItem(0), items); for (auto& item : items) { const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); // If settings item was deleted from the list, // it's need to be deleted from selection array, if it was there if (settings_item != m_objects_model->GetSettingsItem(item) && sel.Index(settings_item) != wxNOT_FOUND) { sel.Remove(settings_item); } } // restore selection: SetSelections(sel); m_prevent_canvas_selection_update = false; } void ObjectList::update_object_menu() { append_menu_items_add_volume(&m_menu_object); } 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--) { if (find(inst_idxs.begin(), inst_idxs.end(), inst_idx) != inst_idxs.end()) continue; model_object->delete_instance(inst_idx); } // Add new object to the object_list add_object_to_list(m_objects->size() - 1); for (std::set::const_reverse_iterator it = inst_idxs.rbegin(); it != inst_idxs.rend(); ++it) { // delete selected instance from the object del_subobject_from_object(obj_idx, *it, itInstance); delete_instance_from_list(obj_idx, *it); } } 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(); const int obj_idx = selection.get_object_idx(); 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() : std::set{ inst_idx }; instances_to_separated_object(obj_idx, inst_idxs); } void ObjectList::rename_item() { const wxDataViewItem item = GetSelection(); if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject))) return ; const wxString new_name = wxGetTextFromUser(_(L("Enter new name"))+":", _(L("Renaming")), m_objects_model->GetName(item), this); if (new_name.IsEmpty()) return; bool is_unusable_symbol = false; std::string chosen_name = Slic3r::normalize_utf8_nfc(new_name.ToUTF8()); const char* unusable_symbols = "<>:/\\|?*\""; for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { is_unusable_symbol = true; } } if (is_unusable_symbol) { show_error(this, _(L("The supplied name is not valid;")) + "\n" + _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); return; } // The icon can't be edited so get its old value and reuse it. wxVariant valueOld; m_objects_model->GetValue(valueOld, item, 0); DataViewBitmapText bmpText; bmpText << valueOld; // But replace the text with the value entered by user. bmpText.SetText(new_name); wxVariant value; value << bmpText; m_objects_model->SetValue(value, item, 0); m_objects_model->ItemChanged(item); update_name_in_model(item); } void ObjectList::fix_through_netfabb() { int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx); wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx); update_item_error_icon(obj_idx, vol_idx); } void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const { const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) : m_objects_model->GetItemByVolumeId(obj_idx, vol_idx); if (!item) return; if (get_mesh_errors_count(obj_idx, vol_idx) == 0) { // if whole object has no errors more, if (get_mesh_errors_count(obj_idx) == 0) // unmark all items in the object m_objects_model->DeleteWarningIcon(vol_idx >= 0 ? m_objects_model->GetParent(item) : item, true); else // unmark fixed item only m_objects_model->DeleteWarningIcon(item); } } void ObjectList::msw_rescale() { const int em = wxGetApp().em_unit(); // update min size !!! A width of control shouldn't be a wxDefaultCoord SetMinSize(wxSize(1, 15 * em)); GetColumn(0)->SetWidth(19 * em); GetColumn(1)->SetWidth( 8 * em); GetColumn(2)->SetWidth( 2 * em); // rescale all icons, used by ObjectList rescale_icons(); // rescale/update existingitems with bitmaps m_objects_model->Rescale(); Layout(); } void ObjectList::ItemValueChanged(wxDataViewEvent &event) { if (event.GetColumn() == 0) update_name_in_model(event.GetItem()); else if (event.GetColumn() == 1) update_extruder_in_config(event.GetItem()); } void ObjectList::OnEditingDone(wxDataViewEvent &event) { if (event.GetColumn() != 0) return; const auto renderer = dynamic_cast(GetColumn(0)->GetRenderer()); if (renderer->WasCanceled()) show_error(this, _(L("The supplied name is not valid;")) + "\n" + _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); } void ObjectList::show_multi_selection_menu() { wxDataViewItemArray sels; GetSelections(sels); for (const wxDataViewItem& item : sels) if (!(m_objects_model->GetItemType(item) & (itVolume | itObject))) // show this menu only for Object(s)/Volume(s) selection return; wxMenu* menu = new wxMenu(); #ifdef __WXOSX__ append_menu_item_delete(menu); #endif //__WXOSX__ if (extruders_count() > 1) append_menu_item(menu, wxID_ANY, _(L("Set extruder for selected items")), _(L("Select extruder number for selected objects and/or parts")), [this](wxCommandEvent&) { extruder_selection(); }, "", menu); PopupMenu(menu); } void ObjectList::extruder_selection() { wxArrayString choices; choices.Add("default"); for (int i = 1; i <= extruders_count(); ++i) choices.Add(wxString::Format("%d", i)); const wxString& selected_extruder = wxGetSingleChoice(_(L("Select extruder number:")), _(L("This extruder will be set for selected items")), choices, 0, this); if (selected_extruder.IsEmpty()) return; const int extruder_num = selected_extruder == "default" ? 0 : atoi(selected_extruder.c_str()); // /* Another variant for an extruder selection */ // extruder_num = wxGetNumberFromUser(_(L("Attention!!! \n" // "It's a possibile to set an extruder number \n" // "for whole Object(s) and/or object Part(s), \n" // "not for an Instance. ")), // _(L("Enter extruder number:")), // _(L("This extruder will be set for selected items")), // 1, 1, 5, this); set_extruder_for_selected_items(extruder_num); } void ObjectList::set_extruder_for_selected_items(const int extruder) const { wxDataViewItemArray sels; GetSelections(sels); for (const wxDataViewItem& item : sels) { DynamicPrintConfig& config = get_item_config(item); if (config.has("extruder")) { if (extruder == 0) config.erase("extruder"); else config.option("extruder")->value = extruder; } else if (extruder > 0) config.set_key_value("extruder", new ConfigOptionInt(extruder)); const wxString extruder_str = extruder == 0 ? wxString ("default") : wxString::Format("%d", config.option("extruder")->value); auto const type = m_objects_model->GetItemType(item); /* We can change extruder for Object/Volume only. * So, if Instance is selected, get its Object item and change it */ m_objects_model->SetValue(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item, 1); const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); wxGetApp().plater()->canvas3D()->ensure_on_bed(obj_idx); } // update scene wxGetApp().plater()->update(); } } //namespace GUI } //namespace Slic3r