#include "libslic3r/libslic3r.h"
#include "libslic3r/PresetBundle.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
#include "GUI_ObjectLayers.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "Plater.hpp"

#include "OptionsGroup.hpp"
#include "Tab.hpp"
#include "wxExtensions.hpp"
#include "libslic3r/Model.hpp"
#include "GLCanvas3D.hpp"
#include "Selection.hpp"

#include <boost/algorithm/string.hpp>
#include "slic3r/Utils/FixModelByWin10.hpp"

#ifdef __WXMSW__
#include "wx/uiaction.h"
#endif /* __WXMSW__ */

namespace Slic3r
{
namespace GUI
{

wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent);

// pt_FFF
static SettingsBundle 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("Wipe options")            , { "wipe_into_infill", "wipe_into_objects" } }
};

// pt_SLA
static SettingsBundle FREQ_SETTINGS_BUNDLE_SLA =
{
    { L("Pad and Support")      , { "supports_enable", "pad_enable" } }
};

// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
static std::vector<std::pair<std::string, std::string>> ADD_VOLUME_MENU_ITEMS = {
//     menu_item Name            menu_item bitmap name
    {L("Add part"),              "add_part" },           // ~ModelVolumeType::MODEL_PART
    {L("Add modifier"),          "add_modifier"},        // ~ModelVolumeType::PARAMETER_MODIFIER
    {L("Add support enforcer"),  "support_enforcer"},    // ~ModelVolumeType::SUPPORT_ENFORCER
    {L("Add support blocker"),   "support_blocker"}      // ~ModelVolumeType::SUPPORT_BLOCKER
};

static PrinterTechnology printer_technology()
{
    return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
}

static const Selection& scene_selection()
{
    return wxGetApp().plater()->canvas3D()->get_selection();
}

// 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();
}

static void take_snapshot(const wxString& snapshot_name) 
{
    Plater* plater = wxGetApp().plater();
    if (plater)
        plater->take_snapshot(snapshot_name);
}

ObjectList::ObjectList(wxWindow* parent) :
    wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE),
    m_parent(parent)
{
    // Fill CATEGORY_ICON
    {
        // ptFFF
        CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap("layers");
        CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap("infill");
        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("ironing");
        CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap("support");
        CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap("time");
        CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap("funnel");
        CATEGORY_ICON[L("Extrusion Width")]          = create_scaled_bitmap("funnel");
        CATEGORY_ICON[L("Wipe options")]             = create_scaled_bitmap("funnel");
//         CATEGORY_ICON[L("Skirt and brim")]          = create_scaled_bitmap("skirt+brim"); 
//         CATEGORY_ICON[L("Speed > Acceleration")]    = create_scaled_bitmap("time");
        CATEGORY_ICON[L("Advanced")]                 = create_scaled_bitmap("wrench");
        // ptSLA
        CATEGORY_ICON[L("Supports")]                 = create_scaled_bitmap("support"/*"sla_supports"*/);
        CATEGORY_ICON[L("Pad")]                      = create_scaled_bitmap("pad");
        CATEGORY_ICON[L("Hollowing")]                = create_scaled_bitmap("hollowing");
    }

    // create control
    create_objects_ctrl();

    init_icons();

    // describe control behavior 
    Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) {
        // detect the current mouse position here, to pass it to list_manipulation() method
        // if we detect it later, the user may have moved the mouse pointer while calculations are performed, and this would mess-up the HitTest() call performed into list_manipulation()
        // see: https://github.com/prusa3d/PrusaSlicer/issues/3802
        const wxPoint mouse_pos = this->get_mouse_position_in_control();

#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(); // It's not necessury anymore #ys_FIXME delete after testing

        // On Windows and Linux:
        // It's not invoked KillFocus event for "temporary" panels (like "Manipulation panel", "Settings", "Layer ranges"),
        // if we change selection in object list.
        // see https://github.com/prusa3d/PrusaSlicer/issues/3303
        // But, if we call SetFocus() for ObjectList it will cause an invoking of a KillFocus event for "temporary" panels  
        this->SetFocus();
#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
         */
#ifdef __WXMSW__
		// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
		int new_selected_column = -1;
#endif //__WXMSW__
        if (wxGetKeyState(WXK_SHIFT))
        {
            wxDataViewItemArray sels;
            GetSelections(sels);
            if (! sels.empty() && sels.front() == m_last_selected_item)
                m_last_selected_item = sels.back();
            else
                m_last_selected_item = event.GetItem();
        }
        else {
  	      	wxDataViewItem    new_selected_item  = event.GetItem();
#ifdef __WXMSW__
			// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
		    wxDataViewItem    item;
		    wxDataViewColumn *col;
		    this->HitTest(this->get_mouse_position_in_control(), item, col);
		    new_selected_column = (col == nullptr) ? -1 : (int)col->GetModelColumn();
	        if (new_selected_item == m_last_selected_item && m_last_selected_column != -1 && m_last_selected_column != new_selected_column) {
	        	// Mouse clicked on another column of the active row. Simulate keyboard enter to enter the editing mode of the current column.
	        	wxUIActionSimulator sim;
				sim.Char(WXK_RETURN);
	        }
#endif //__WXMSW__
	        m_last_selected_item = new_selected_item;
        }
#ifdef __WXMSW__
        m_last_selected_column = new_selected_column;
#endif //__WXMSW__

        selection_changed();
#ifndef __WXMSW__
        set_tooltip_for_item(this->get_mouse_position_in_control());
#endif //__WXMSW__

#ifndef __WXOSX__
        list_manipulation(mouse_pos);
#endif //__WXOSX__
    });

#ifdef __WXOSX__
    // Key events are not correctly processed by the wxDataViewCtrl on OSX.
    // Our patched wxWidgets process the keyboard accelerators.
    // On the other hand, using accelerators will break in-place editing on Windows & Linux/GTK (there is no in-place editing working on OSX for wxDataViewCtrl for now).
//    Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this);
    {
        // Accelerators
        wxAcceleratorEntry entries[10];
        entries[0].Set(wxACCEL_CTRL, (int) 'C',    wxID_COPY);
        entries[1].Set(wxACCEL_CTRL, (int) 'X',    wxID_CUT);
        entries[2].Set(wxACCEL_CTRL, (int) 'V',    wxID_PASTE);
        entries[3].Set(wxACCEL_CTRL, (int) 'A',    wxID_SELECTALL);
        entries[4].Set(wxACCEL_CTRL, (int) 'Z',    wxID_UNDO);
        entries[5].Set(wxACCEL_CTRL, (int) 'Y',    wxID_REDO);
        entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE);
        entries[7].Set(wxACCEL_NORMAL, WXK_BACK,   wxID_DELETE);
        entries[8].Set(wxACCEL_NORMAL, int('+'),   wxID_ADD);
        entries[9].Set(wxACCEL_NORMAL, int('-'),   wxID_REMOVE);
        wxAcceleratorTable accel(10, entries);
        SetAcceleratorTable(accel);

        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy();                      }, wxID_COPY);
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste();                     }, wxID_PASTE);
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children();  }, wxID_SELECTALL);
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove();                    }, wxID_DELETE);
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo();  					}, wxID_UNDO);
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo();                    	}, wxID_REDO);
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instances();        }, wxID_ADD);
        this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instances();        }, wxID_REMOVE);
    }
#else //__WXOSX__
    Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
#endif

#ifdef __WXMSW__
    GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) {
        set_tooltip_for_item(this->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);

#ifdef __WXMSW__
    Bind(wxEVT_DATAVIEW_ITEM_EDITING_STARTED, &ObjectList::OnEditingStarted,  this);
#endif /* __WXMSW__ */
    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()); });

    Bind(wxEVT_SIZE, ([this](wxSizeEvent &e) { 
#ifdef __WXGTK__
	// On GTK, the EnsureVisible call is postponed to Idle processing (see wxDataViewCtrl::m_ensureVisibleDefered).
	// So the postponed EnsureVisible() call is planned for an item, which may not exist at the Idle processing time, if this wxEVT_SIZE
	// event is succeeded by a delete of the currently active item. We are trying our luck by postponing the wxEVT_SIZE triggered EnsureVisible(),
	// which seems to be working as of now.
    this->CallAfter([this](){ ensure_current_item_visible(); });
#else
    ensure_current_item_visible();
#endif
	e.Skip();
	}));
}

ObjectList::~ObjectList()
{
}

void ObjectList::set_min_height()
{
    /* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
    * change min hight of object list to the normal min value (20 * wxGetApp().em_unit())
    * after first whole Mainframe updating/layouting
    */
    const int list_min_height = 20 * wxGetApp().em_unit();
    if (this->GetMinSize().GetY() > list_min_height)
        this->SetMinSize(wxSize(-1, list_min_height));
}


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 (20 * 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

    const int em = wxGetApp().em_unit();

    // column ItemName(Icon+Text) of the view control: 
    // And Icon can be consisting of several bitmaps
    BitmapTextRenderer* bmp_text_renderer = new BitmapTextRenderer();
    bmp_text_renderer->set_can_create_editor_ctrl_function([this]() {
        return m_objects_model->GetItemType(GetSelection()) & (itVolume | itObject);
    });
    AppendColumn(new wxDataViewColumn(_L("Name"), bmp_text_renderer,
        colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE));

    // column PrintableProperty (Icon) of the view control:
    AppendBitmapColumn(" ", colPrint, wxDATAVIEW_CELL_INERT, 3*em,
        wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);

    // column Extruder of the view control:
    BitmapChoiceRenderer* bmp_choice_renderer = new BitmapChoiceRenderer();
    bmp_choice_renderer->set_can_create_editor_ctrl_function([this]() {
        return m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer | itObject);
    });
    AppendColumn(new wxDataViewColumn(_L("Extruder"), bmp_choice_renderer,
        colExtruder, 8*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE));

    // column ItemEditing of the view control:
    AppendBitmapColumn(_L("Editing"), colEditing, wxDATAVIEW_CELL_INERT, 3*em,
        wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);

    // For some reason under OSX on 4K(5K) monitors in wxDataViewColumn constructor doesn't set width of column.
    // Therefore, force set column width.
    if (wxOSX)
    {
        GetColumn(colName)->SetWidth(20*em);
        GetColumn(colPrint)->SetWidth(3*em);
        GetColumn(colExtruder)->SetWidth(8*em);
        GetColumn(colEditing) ->SetWidth(7*em);
    }
}

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);
    create_default_popupmenu(&m_menu_default);
}

void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/)
{
    const wxDataViewItem item = input_item == wxDataViewItem(nullptr) ? 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;
}

void ObjectList::get_selection_indexes(std::vector<int>& obj_idxs, std::vector<int>& vol_idxs)
{
    wxDataViewItemArray sels;
    GetSelections(sels);
    assert(!sels.IsEmpty());

    if (m_objects_model->GetItemType(sels[0]) & itVolume) {
        for (wxDataViewItem item : sels) {
            obj_idxs.emplace_back(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)));

            assert(m_objects_model->GetItemType(item) & itVolume);
            vol_idxs.emplace_back(m_objects_model->GetVolumeIdByItem(item));
        }
    }
    else {
        for (wxDataViewItem item : sels) {
            const ItemType type = m_objects_model->GetItemType(item);
            assert(type & (itObject | itInstance | itInstanceRoot));

            obj_idxs.emplace_back(type & itObject ? m_objects_model->GetIdByItem(item) :
                                  m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)));
        }
    }

    std::sort(obj_idxs.begin(), obj_idxs.end(), std::greater<int>());
    obj_idxs.erase(std::unique(obj_idxs.begin(), obj_idxs.end()), obj_idxs.end());
}

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):")), errors) + "\n";

    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<std::string, int> 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 += from_u8((boost::format("\t%1% %2%\n") % error.second % _utf8(error.first)).str());

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

    /* GetMainWindow() return window, associated with wxDataViewCtrl.
     * And for this window we should to set tooltips.
     * Just this->SetToolTip(tooltip) => has no effect.
     */

    if (!item || GetSelectedItemsCount() > 1)
    {
        GetMainWindow()->SetToolTip(""); // hide tooltip
        return;
    }

    wxString tooltip = "";

    if (col->GetTitle() == _(L("Editing")))
#ifdef __WXOSX__
        tooltip = _(L("Right button click the icon to change the object settings"));
#else
        tooltip = _(L("Click the icon to change the object settings"));
#endif //__WXMSW__
    else if (col->GetTitle() == " ")
#ifdef __WXOSX__
        tooltip = _(L("Right button click the icon to change the object printable property"));
#else
        tooltip = _(L("Click the icon to change the object printable property"));
#endif //__WXMSW__
    else if (col->GetTitle() == _("Name") && (pt.x >= 2 * wxGetApp().em_unit() && pt.x <= 4 * wxGetApp().em_unit()))
    {
        int obj_idx, vol_idx;
        get_selected_item_indexes(obj_idx, vol_idx, item);
        tooltip = get_mesh_errors_list(obj_idx, vol_idx);
    }
    
    GetMainWindow()->SetToolTip(tooltip);
}

int ObjectList::get_selected_obj_idx() const
{
    if (GetSelectedItemsCount() == 1)
        return m_objects_model->GetIdByItem(m_objects_model->GetTopParent(GetSelection()));

    return -1;
}

ModelConfig& ObjectList::get_item_config(const wxDataViewItem& item) const 
{
    assert(item);
    const ItemType type = m_objects_model->GetItemType(item);

    const int obj_idx = m_objects_model->GetObjectIdByItem(item);
    const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1;

    assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0));
    return type & itVolume ?(*m_objects)[obj_idx]->volumes[vol_idx]->config :
           type & itLayer  ?(*m_objects)[obj_idx]->layer_config_ranges[m_objects_model->GetLayerRangeByItem(item)] :
                            (*m_objects)[obj_idx]->config;
}

void ObjectList::update_extruder_values_for_items(const size_t max_extruder)
{
    for (size_t 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") ||
            size_t(object->config.extruder()) > max_extruder)
            extruder = _(L("default"));
        else
            extruder = wxString::Format("%d", object->config.extruder());

        m_objects_model->SetExtruder(extruder, item);

        if (object->volumes.size() > 1) {
            for (size_t id = 0; id < object->volumes.size(); id++) {
                item = m_objects_model->GetItemByVolumeId(i, id);
                if (!item) continue;
                if (!object->volumes[id]->config.has("extruder") ||
                    size_t(object->volumes[id]->config.extruder()) > max_extruder)
                    extruder = _(L("default"));
                else
                    extruder = wxString::Format("%d", object->volumes[id]->config.extruder()); 

                m_objects_model->SetExtruder(extruder, item);
            }
        }
    }
}

void ObjectList::update_objects_list_extruder_column(size_t extruders_count)
{
    if (printer_technology() == ptSLA)
        extruders_count = 1;

    m_prevent_update_extruder_in_config = true;

    if (m_objects && extruders_count > 1)
        update_extruder_values_for_items(extruders_count);

    update_extruder_colors();

    // 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(colEditing)->SetWidth(25);

    m_prevent_update_extruder_in_config = false;
}

void ObjectList::update_extruder_colors()
{
    m_objects_model->UpdateColumValues(colExtruder);
}

void ObjectList::set_extruder_column_hidden(const bool hide) const
{
    GetColumn(colExtruder)->SetHidden(hide);
}

void ObjectList::update_extruder_in_config(const wxDataViewItem& item)
{
    if (m_prevent_update_extruder_in_config)
        return;

    const ItemType item_type = m_objects_model->GetItemType(item);
    if (item_type & itObject) {
        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->GetTopParent(item));
        if (item_type & itVolume)
        {
        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;
        }
        else if (item_type & itLayer)
            m_config = &get_item_config(item);
    }

    if (!m_config)
        return;

    take_snapshot(_(L("Change Extruder")));

    const int extruder = m_objects_model->GetExtruderNumber(item);
    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;
    const int volume_id = m_objects_model->GetVolumeIdByItem(item);

    take_snapshot(volume_id < 0 ? _(L("Rename Object")) : _(L("Rename Sub-object")));

    if (m_objects_model->GetItemType(item) & itObject) {
        (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data();
        return;
    }

    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(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)        ].second);
    m_bmp_modifiermesh      = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second);
    m_bmp_support_enforcer  = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)  ].second);
    m_bmp_support_blocker   = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)   ].second); 

    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(this, "exclamation");
    // Set warning bitmap for the model
    m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp());

    // init bitmap for "Add Settings" context menu
    m_bmp_cog               = ScalableBitmap(this, "cog");
}

void ObjectList::msw_rescale_icons()
{
    m_bmp_vector.clear();
    m_bmp_vector.reserve(4); // bitmaps for different types of parts 
    for (ScalableBitmap* bitmap : { &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_cog.msw_rescale();


    // Update CATEGORY_ICON according to new scale
    {
        // ptFFF
        CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap("layers");
        CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap("infill");
        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("ironing");
        CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap("support");
        CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap("time");
        CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap("funnel");
        CATEGORY_ICON[L("Extrusion Width")]          = create_scaled_bitmap("funnel");
        CATEGORY_ICON[L("Wipe options")]             = create_scaled_bitmap("funnel");
//         CATEGORY_ICON[L("Skirt and brim")]          = create_scaled_bitmap("skirt+brim"); 
//         CATEGORY_ICON[L("Speed > Acceleration")]    = create_scaled_bitmap("time");
        CATEGORY_ICON[L("Advanced")]                 = create_scaled_bitmap("wrench");
        // ptSLA
        CATEGORY_ICON[L("Supports")]                 = create_scaled_bitmap("support"/*"sla_supports"*/);
        CATEGORY_ICON[L("Pad")]                      = create_scaled_bitmap("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);
    }

    if (const wxDataViewItem item = GetSelection())
    {
        const ItemType type = m_objects_model->GetItemType(item);
        // to correct visual hints for layers editing on the Scene
        if (type & (itLayer|itLayerRoot)) {
            wxGetApp().obj_layers()->reset_selection();
            
            if (type & itLayerRoot)
                wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
            else {
                wxGetApp().obj_layers()->set_selectable_range(m_objects_model->GetLayerRangeByItem(item));
                wxGetApp().obj_layers()->update_scene_from_editor_selection();
            }
        }
    }

    part_selection_changed();
}

void ObjectList::copy_layers_to_clipboard()
{
    wxDataViewItemArray sel_layers;
    GetSelections(sel_layers);

    const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers.front());
    if (obj_idx < 0 || (int)m_objects->size() <= obj_idx)
        return;

    const t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
    t_layer_config_ranges& cache_ranges = m_clipboard.get_ranges_cache();

    if (sel_layers.Count() == 1 && m_objects_model->GetItemType(sel_layers.front()) & itLayerRoot)
    {
        cache_ranges.clear();
        cache_ranges = ranges;
        return;
    }

    for (const auto layer_item : sel_layers)
        if (m_objects_model->GetItemType(layer_item) & itLayer) {
            auto range = m_objects_model->GetLayerRangeByItem(layer_item);
            auto it = ranges.find(range);
            if (it != ranges.end())
                cache_ranges[it->first] = it->second;
        }
}

void ObjectList::paste_layers_into_list()
{
    const int obj_idx = m_objects_model->GetObjectIdByItem(GetSelection());
    t_layer_config_ranges& cache_ranges = m_clipboard.get_ranges_cache();

    if (obj_idx < 0 || (int)m_objects->size() <= obj_idx || 
        cache_ranges.empty() || printer_technology() == ptSLA)
        return;

    const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx);
    wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item);
    if (layers_item)
        m_objects_model->Delete(layers_item);

    t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;

    // and create Layer item(s) according to the layer_config_ranges
    for (const auto range : cache_ranges)
        ranges.emplace(range);

    layers_item = add_layer_root_item(object_item);

    changed_object(obj_idx);

    select_item(layers_item);
#ifndef __WXOSX__
    selection_changed();
#endif //no __WXOSX__
}

void ObjectList::copy_settings_to_clipboard()
{
    wxDataViewItem item = GetSelection();
    assert(item.IsOk());
    if (m_objects_model->GetItemType(item) & itSettings)
        item = m_objects_model->GetParent(item);

    m_clipboard.get_config_cache() = get_item_config(item).get();
}

void ObjectList::paste_settings_into_list()
{
    wxDataViewItem item = GetSelection();
    assert(item.IsOk());
    if (m_objects_model->GetItemType(item) & itSettings)
        item = m_objects_model->GetParent(item);

    ItemType item_type = m_objects_model->GetItemType(item);
    if(!(item_type & (itObject | itVolume |itLayer)))
        return;

    DynamicPrintConfig& config_cache = m_clipboard.get_config_cache();
    assert(!config_cache.empty());

    auto keys = config_cache.keys();
    auto part_options = get_options(true);

    for (const std::string& opt_key: keys) {
        if (item_type & (itVolume | itLayer) &&
            std::find(part_options.begin(), part_options.end(), opt_key) == part_options.end())
            continue; // we can't to add object specific options for the part's(itVolume | itLayer) config 

        const ConfigOption* option = config_cache.option(opt_key);
        if (option)
            m_config->set_key_value(opt_key, option->clone());
    }

    // Add settings item for object/sub-object and show them 
    show_settings(add_settings_item(item, &m_config->get()));
}

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;

    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, wxString::FromUTF8(volume->name.c_str()), volume->type(), 
            volume->get_mesh_errors_count()>0 ,
            volume->config.has("extruder") ? volume->config.extruder() : 0);
        add_settings_item(vol_item, &volume->config.get());
        items.Add(vol_item);
    }

    changed_object(obj_idx);

    if (items.size() > 1)
    {
        m_selection_mode = smVolume;
        m_last_selected_item = wxDataViewItem(nullptr);
    }

    select_items(items);
//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
    selection_changed();
//#endif //no __WXOSX__ //__WXMSW__
}

void ObjectList::paste_objects_into_list(const std::vector<size_t>& 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__
}

#ifdef __WXOSX__
/*
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();
}
*/
#endif /* __WXOSX__ */

void ObjectList::OnContextMenu(wxDataViewEvent& evt)
{
    // The mouse position returned by get_mouse_position_in_control() here is the one at the time the mouse button is released (mouse up event)
    wxPoint mouse_pos = this->get_mouse_position_in_control();

    // Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list
    GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
    bool evt_context_menu = (canvas != nullptr) ? !canvas->is_mouse_dragging() : true;
    if (!evt_context_menu)
        canvas->mouse_up_cleanup();

    list_manipulation(mouse_pos, evt_context_menu);
}

void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_menu/* = false*/)
{
    // Interesting fact: when mouse_pos.x < 0, HitTest(mouse_pos, item, col) returns item = null, but column = last column.
    // So, when mouse was moved to scene immediately after clicking in ObjectList, in the scene will be shown context menu for the Editing column.
    // see: https://github.com/prusa3d/PrusaSlicer/issues/3802
    if (mouse_pos.x < 0)
        return;

    wxDataViewItem item;
    wxDataViewColumn* col = nullptr;
    HitTest(mouse_pos, item, col);

    if (m_extruder_editor)
        m_extruder_editor->Hide();

    /* Note: Under OSX right click doesn't send "selection changed" event.
     * It means that Selection() will be return still previously selected item.
     * Thus under OSX we should force UnselectAll(), when item and col are nullptr,
     * and select new item otherwise.
     */

    if (!item) {
        if (col == nullptr) {
            if (wxOSX && !multiple_selection())
                UnselectAll();
            else if (!evt_context_menu) 
                // Case, when last item was deleted and under GTK was called wxEVT_DATAVIEW_SELECTION_CHANGED,
                // which invoked next list_manipulation(false)
                return;
        }

        if (evt_context_menu) {
            show_context_menu(evt_context_menu);
            return;
        }
    }

    if (wxOSX && item && col) {
        wxDataViewItemArray sels;
        GetSelections(sels);
        UnselectAll();
        if (sels.Count() > 1)
            SetSelections(sels);
        else
            Select(item);
    }

    if (col != nullptr) 
    {
	    const wxString title = col->GetTitle();
	    if (title == " ")
	        toggle_printable_state(item);
	    else if (title == _("Editing"))
	        show_context_menu(evt_context_menu);
	    else if (title == _("Name"))
	    {
	        if (wxOSX)
	            show_context_menu(evt_context_menu); // return context menu under OSX (related to #2909)

	        if (is_windows10())
	        {
	            int obj_idx, vol_idx;
	            get_selected_item_indexes(obj_idx, vol_idx, item);

	            if (get_mesh_errors_count(obj_idx, vol_idx) > 0 && 
	                mouse_pos.x > 2 * wxGetApp().em_unit() && mouse_pos.x < 4 * wxGetApp().em_unit())
	                fix_through_netfabb();
	        }
	    }
	    // workaround for extruder editing under OSX 
	    else if (wxOSX && evt_context_menu && title == _("Extruder"))
	        extruder_editing();
	}

#ifndef __WXMSW__
    GetMainWindow()->SetToolTip(""); // hide tooltip
#endif //__WXMSW__
}

void ObjectList::show_context_menu(const bool evt_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();
    wxMenu* menu {nullptr};
    if (item)
    {
        const ItemType type = m_objects_model->GetItemType(item);
        if (!(type & (itObject | itVolume | itLayer | itInstance)))
            return;

        menu = type & itInstance ? &m_menu_instance :
                       type & itLayer ? &m_menu_layer :
                       m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part :
                       printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object;

        if (type & (itObject | itVolume))
            append_menu_item_convert_unit(menu);
        if (!(type & itInstance))
            append_menu_item_settings(menu);
    }
    else if (evt_context_menu)
        menu = &m_menu_default;

    if (menu)
        wxGetApp().plater()->PopupMenu(menu);
}

void ObjectList::extruder_editing()
{
    wxDataViewItem item = GetSelection();
    if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject)))
        return;

    const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5;

    wxPoint pos = this->get_mouse_position_in_control();
    wxSize size = wxSize(column_width, -1);
    pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5;
    pos.y -= GetTextExtent("m").y;

    apply_extruder_selector(&m_extruder_editor, this, L("default"), pos, size);

    m_extruder_editor->SetSelection(m_objects_model->GetExtruderNumber(item));
    m_extruder_editor->Show();

    auto set_extruder = [this]()
    {
        wxDataViewItem item = GetSelection();
        if (!item) return;

        const int selection = m_extruder_editor->GetSelection();
        if (selection >= 0) 
            m_objects_model->SetExtruder(m_extruder_editor->GetString(selection), item);

        m_extruder_editor->Hide();
        update_extruder_in_config(item);
    };

    // to avoid event propagation to other sidebar items
    m_extruder_editor->Bind(wxEVT_COMBOBOX, [set_extruder](wxCommandEvent& evt)
    {
        set_extruder();
        evt.StopPropagation();
    });
}

void ObjectList::copy()
{
    wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY));
}

void ObjectList::paste()
{
    wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE));
}

bool ObjectList::copy_to_clipboard()
{
    wxDataViewItemArray sels;
    GetSelections(sels);
    if (sels.IsEmpty())
        return false;
    ItemType type = m_objects_model->GetItemType(sels.front());
    if (!(type & (itSettings | itLayer | itLayerRoot))) {
        m_clipboard.reset();
        return false;
    }

    if (type & itSettings)
        copy_settings_to_clipboard();
    if (type & (itLayer | itLayerRoot))
        copy_layers_to_clipboard();

    m_clipboard.set_type(type);
    return true;
}

bool ObjectList::paste_from_clipboard()
{
    if (!(m_clipboard.get_type() & (itSettings | itLayer | itLayerRoot))) {
        m_clipboard.reset();
        return false;
    }

    if (m_clipboard.get_type() & itSettings)
        paste_settings_into_list();
    if (m_clipboard.get_type() & (itLayer | itLayerRoot))
        paste_layers_into_list();

    return true;
}

void ObjectList::undo()
{
	wxGetApp().plater()->undo();
}

void ObjectList::redo()
{
	wxGetApp().plater()->redo();	
}

void ObjectList::increase_instances()
{
    wxGetApp().plater()->increase_instances(1);
}

void ObjectList::decrease_instances()
{
    wxGetApp().plater()->decrease_instances(1);
}

#ifndef __WXOSX__
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 (event.GetKeyCode() == WXK_F5)
        wxGetApp().plater()->reload_all_from_disk();
    else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/))
        select_item_all_children();
    else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) 
        copy();
    else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL))
        paste();
    else if (wxGetKeyState(wxKeyCode('Y')) && wxGetKeyState(WXK_CONTROL))
        redo();
    else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL))
        undo();
    else if (event.GetUnicodeKey() == '+')
        increase_instances();
    else if (event.GetUnicodeKey() == '-')
        decrease_instances();
    else
        event.Skip();
}
#endif /* __WXOSX__ */

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)) ) {
        event.Veto();
        return;
    }
   
    const ItemType& type = m_objects_model->GetItemType(item);
    if (!(type & (itVolume | itObject | itInstance))) {
        event.Veto();
        return;
    }

    if (mult_sel)
    {
        m_dragged_data.init(m_objects_model->GetObjectIdByItem(item),type);
        std::set<int>& 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 if (type & itObject)
        m_dragged_data.init(m_objects_model->GetIdByItem(item), type);
    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 vaSome textlue,
     * 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 
{
    // move instance(s) or object on "empty place" of ObjectList
    if ( (m_dragged_data.type() & (itInstance | itObject)) && !item.IsOk() )
        return true;

    // type of moved item should be the same as a "destination" item
    if (!item.IsOk() || !(m_dragged_data.type() & (itVolume|itObject)) || 
        m_objects_model->GetItemType(item) != m_dragged_data.type() )
        return false;

    // move volumes inside one object only
    if (m_dragged_data.type() & itVolume)
        return m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item);

    return true;
}

void ObjectList::OnDropPossible(wxDataViewEvent &event)
{
    const wxDataViewItem& item = event.GetItem();

    if (!can_drop(item)) {
        event.Veto();
        m_prevent_list_events = false;
    }
}

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)
    {
        Plater::TakeSnapshot snapshot(wxGetApp().plater(),_(L("Instances to Separated Objects")));
        instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs());
        m_dragged_data.clear();
        return;
    }

// 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"Some text
//     * 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__

    take_snapshot(_((m_dragged_data.type() == itVolume) ? L("Volumes in Object reordered") : L("Object reordered")));

    if (m_dragged_data.type() & itVolume)
    {
        int from_volume_id = m_dragged_data.sub_obj_idx();
        int to_volume_id   = m_objects_model->GetVolumeIdByItem(item);
        int delta = to_volume_id < from_volume_id ? -1 : 1;

        auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes;

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

    }
    else if (m_dragged_data.type() & itObject)
    {
        int from_obj_id = m_dragged_data.obj_idx();
        int to_obj_id   = item.IsOk() ? m_objects_model->GetIdByItem(item) : ((int)m_objects->size()-1);
        int delta = to_obj_id < from_obj_id ? -1 : 1;

        int cnt = 0;
        for (int id = from_obj_id; cnt < abs(from_obj_id - to_obj_id); id += delta, cnt++)
            std::swap((*m_objects)[id], (*m_objects)[id + delta]);

        select_item(m_objects_model->ReorganizeObjects(from_obj_id, to_obj_id));
    }

    changed_object(m_dragged_data.obj_idx());

    m_dragged_data.clear();

    wxGetApp().plater()->set_current_canvas_as_dirty();
}


// Context Menu

std::vector<std::string> 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<std::string> obj_options = obj_config.keys();
        options.insert(options.end(), obj_options.begin(), obj_options.end());
    }
    return options;
}
    
const std::vector<std::string>& ObjectList::get_options_for_bundle(const wxString& bundle_name)
{
    const SettingsBundle& 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
    SettingsBundle& bundle_quick = printer_technology() == ptSLA ?
                                       m_freq_settings_sla: m_freq_settings_fff;

    for (auto& it : bundle_quick)
    {
        if ( bundle_name == from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()) )
            return it.second;
    }
#endif

	static std::vector<std::string> empty;
	return empty;
}

static bool improper_category(const std::string& category, const int extruders_cnt, const bool is_object_settings = true)
{
    return  category.empty() || 
            (extruders_cnt == 1 && (category == "Extruders" || category == "Wipe options" )) ||
            (!is_object_settings && category == "Support material");
}

static bool is_object_item(ItemType item_type)
{
    return item_type & itObject || item_type & itInstance ||
            // multi-selection in ObjectList, but full_object in Selection
            (item_type == itUndef && scene_selection().is_single_full_object());
}

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 (improper_category(category, extruders_cnt, !is_part))
            continue;

        const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label;
        std::pair<std::string, std::string> option_label(option, label);
        std::vector< std::pair<std::string, std::string> > 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;

    /* If we try to add settings for object/part from 3Dscene,
     * for the second try there is selected ItemSettings in ObjectList.
     * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes
     */
    const wxDataViewItem selected_item = GetSelection();
    wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item;

    const ItemType item_type = m_objects_model->GetItemType(item);

    settings_menu_hierarchy settings_menu;
    const bool is_part = item_type & (itVolume | itLayer);
    get_options_menu(settings_menu, is_part);
    std::vector< std::pair<std::string, std::string> > *settings_list = nullptr;

    if (!m_config)
        m_config = &get_item_config(item);

    assert(m_config);
    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"
        SettingsBundle& freq_settings = printer_technology() == ptSLA ?
                                            m_freq_settings_sla : m_freq_settings_fff;
        bool changed_existing = false;

        std::vector<std::string> tmp_freq_cat = {};
        
        for (auto& cat : freq_settings)
        {
            if (_(cat.first) == category_name)
            {
                std::vector<std::string>& 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::string> {};

                    std::vector<std::string>& 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

    const wxString snapshot_text =  item_type & itLayer   ? _(L("Add Settings for Layers")) :
                                    item_type & itVolume  ? _(L("Add Settings for Sub-object")) :
                                                            _(L("Add Settings for Object"));
    take_snapshot(snapshot_text);

    std::vector <std::string> 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/sub-object and show them 
    if (!(item_type & (itObject | itVolume | itLayer)))
        item = m_objects_model->GetTopParent(item);
    show_settings(add_settings_item(item, &m_config->get()));
}

void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
{
    std::vector<std::string> options = get_options_for_bundle(bundle_name);
    const Selection& selection = scene_selection();
    const wxDataViewItem sel_item = // when all instances in object are selected
                                    GetSelectedItemsCount() > 1 && selection.is_single_full_object() ? 
                                    m_objects_model->GetItemById(selection.get_object_idx()) : 
                                    GetSelection();

    /* If we try to add settings for object/part from 3Dscene,
     * for the second try there is selected ItemSettings in ObjectList.
     * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes
     */
    wxDataViewItem item = m_objects_model->GetItemType(sel_item) & itSettings ? m_objects_model->GetParent(sel_item) : sel_item;
    const ItemType item_type = m_objects_model->GetItemType(item);

    /* Because of we couldn't edited layer_height for ItVolume from settings list,
     * correct options according to the selected item type :
     * remove "layer_height" option
     */
    if ((item_type & itVolume) && bundle_name == _("Layers and Perimeters")) {
        const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height");
        if (layer_height_it != options.end())
            options.erase(layer_height_it);
    }

    if (!m_config)
        m_config = &get_item_config(item);

    assert(m_config);
    auto opt_keys = m_config->keys();

    const wxString snapshot_text = item_type & itLayer  ? _(L("Add Settings Bundle for Height range")) :
                                   item_type & itVolume ? _(L("Add Settings Bundle for Sub-object")) :
                                                          _(L("Add Settings Bundle for Object"));
    take_snapshot(snapshot_text);

    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/sub-object and show them 
    if (!(item_type & (itObject | itVolume | itLayer)))
        item = m_objects_model->GetTopParent(item);
    show_settings(add_settings_item(item, &m_config->get()));
}

void ObjectList::show_settings(const wxDataViewItem settings_item)
{
    if (!settings_item)
        return;

    select_item(settings_item);
    
    // update object selection on Plater
    if (!m_prevent_canvas_selection_update)
        update_selections_on_canvas();
}

wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) {
    auto sub_menu = new wxMenu;

    if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) {
    append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "",
        [this, type](wxCommandEvent&) { load_subobject(type); }, "", menu);
    sub_menu->AppendSeparator();
    }

    for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") })
    {
        if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0)
            continue;
        append_menu_item(sub_menu, wxID_ANY, _(item), "",
            [this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu);
    }

    return sub_menu;
}

void ObjectList::append_menu_items_add_volume(wxMenu* menu)
{
    // Update "add" items(delete old & create new)  settings popupmenu
    for (auto& item : ADD_VOLUME_MENU_ITEMS){
        const auto settings_id = menu->FindItem(_(item.first));
        if (settings_id != wxNOT_FOUND)
            menu->Destroy(settings_id);
    }

    const ConfigOptionMode mode = wxGetApp().get_mode();

    wxWindow* parent = wxGetApp().plater();

    if (mode == comAdvanced) {
        append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "",
            [this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); }, 
            ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr,
            [this]() { return is_instance_or_object_selected(); }, parent);
    }
    if (mode == comSimple) {
        append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "",
            [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); },
            ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr,
            [this]() { return is_instance_or_object_selected(); }, parent);
        append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].first), "",
            [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); },
            ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second, nullptr,
            [this]() { return is_instance_or_object_selected(); }, parent);

        return;
    }
    
    for (size_t type = (mode == comExpert ? 0 : 1) ; type < ADD_VOLUME_MENU_ITEMS.size(); type++)
    {
        auto& item = ADD_VOLUME_MENU_ITEMS[type];

        wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type));
        append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second,
            [this]() { return is_instance_or_object_selected(); }, parent);
    }
}

wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) 
{
    return append_menu_item(menu, wxID_ANY, _(L("Split to parts")), "",
        [this](wxCommandEvent&) { split(); }, "split_parts_SMALL", menu, 
        [this]() { return is_splittable(); }, wxGetApp().plater());
}

bool ObjectList::is_instance_or_object_selected()
{
    const Selection& selection = scene_selection();
    return selection.is_single_full_instance() || selection.is_single_full_object();
}

wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent)
{
    return append_menu_item(menu, wxID_ANY, _(L("Height range Modifier")), "",
        [this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu,
        [this]() { return is_instance_or_object_selected(); }, parent);
}

wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) 
{
    MenuWithSeparators* menu = dynamic_cast<MenuWithSeparators*>(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(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()));
        if (settings_id != wxNOT_FOUND)
            menu->Destroy(settings_id);
    }
    for (auto& it : m_freq_settings_sla)
    {
        settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()));
        if (settings_id != wxNOT_FOUND)
            menu->Destroy(settings_id);
    }
#endif
    menu->DestroySeparators(); // delete old separators

    // If there are selected more then one instance but not all of them
    // don't add settings menu items
    const Selection& selection = scene_selection();
    if ((selection.is_multiple_full_instance() && !selection.is_single_full_object()) ||
        selection.is_multiple_volume() || selection.is_mixed() ) // more than one volume(part) is selected on the scene
        return nullptr;

    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
    const ItemType item_type = m_objects_model->GetItemType(GetSelection());
    if (item_type == itUndef && !selection.is_single_full_object())
        return nullptr;
    const bool is_object_settings = is_object_item(item_type);
    create_freq_settings_popupmenu(menu, is_object_settings);

    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, wxWindow* parent/* = nullptr*/)
{
    return append_menu_item(menu, wxID_ANY, _(L("Change type")), "",
        [this](wxCommandEvent&) { change_part_type(); }, "", menu, 
        [this]() {
            wxDataViewItem item = GetSelection();
            return item.IsOk() || m_objects_model->GetItemType(item) == itVolume;
        }, parent);
}

wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent)
{
    wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "",
        [this](wxCommandEvent&) { split_instances(); }, "", 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
     */
    parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt)
    {
        const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
        evt.SetText(selection.is_single_full_object() ?
        _(L("Set as a Separated Objects")) : _(L("Set as a Separated Object")));

        evt.Enable(wxGetApp().plater()->can_set_instance_to_object());
    }, menu_item->GetId());

    return menu_item;
}

wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* /*parent*/)
{
    return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) {
        const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
        wxDataViewItem item;
        if (GetSelectedItemsCount() > 1 && selection.is_single_full_object())
            item = m_objects_model->GetItemById(selection.get_object_idx());
        else
            item = GetSelection();

        if (item)
            toggle_printable_state(item);
    }, menu);
}

void ObjectList::append_menu_items_osx(wxMenu* 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;
    Plater* plater = wxGetApp().plater();
    wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Fix through the Netfabb")), "",
        [this](wxCommandEvent&) { fix_through_netfabb(); }, "", menu, 
        [plater]() {return plater->can_fix_through_netfabb(); }, plater);
    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_reload_from_disk(wxMenu* menu) const
{
    append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")),
        [this](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu,
        []() { return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater());
}

void ObjectList::append_menu_item_change_extruder(wxMenu* menu)
{
    const std::vector<wxString> names = {_(L("Change extruder")), _(L("Set extruder for selected items")) };
    // Delete old menu item
    for (const wxString& name : names) {
        const int item_id = menu->FindItem(name);
        if (item_id != wxNOT_FOUND)
            menu->Destroy(item_id);
    }

    const int extruders_cnt = extruders_count();
    if (extruders_cnt <= 1)
        return;

    wxDataViewItemArray sels;
    GetSelections(sels);
    if (sels.IsEmpty())
        return;

    std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
    wxMenu* extruder_selection_menu = new wxMenu();
    const wxString& name = sels.Count()==1 ? names[0] : names[1];

    int initial_extruder = -1; // negative value for multiple object/part selection
    if (sels.Count()==1) {
        const ModelConfig &config = get_item_config(sels[0]);
        initial_extruder = config.has("extruder") ? config.extruder() : 0;
    }

    for (int i = 0; i <= extruders_cnt; i++)
    {
        bool is_active_extruder = i == initial_extruder;
        int icon_idx = i == 0 ? 0 : i - 1;

        const wxString& item_name = (i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i)) +
                                    (is_active_extruder ? " (" + _(L("active")) + ")" : "");

        append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "",
            [this, i](wxCommandEvent&) { set_extruder_for_selected_items(i); }, *icons[icon_idx], menu,
            [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater());

    }

    menu->AppendSubMenu(extruder_selection_menu, name);
}

void ObjectList::append_menu_item_delete(wxMenu* menu)
{
    append_menu_item(menu, wxID_ANY, _(L("Delete")), "",
        [this](wxCommandEvent&) { remove(); }, "", menu);
}

void ObjectList::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu)
{
    append_menu_item(menu, wxID_ANY, _(L("Scale to print volume")), _(L("Scale the selected object to fit the print volume")),
        [](wxCommandEvent&) { wxGetApp().plater()->scale_selection_to_fit_print_volume(); }, "", menu);
}

void ObjectList::append_menu_item_convert_unit(wxMenu* menu, int insert_pos/* = 1*/)
{
    std::vector<int> obj_idxs, vol_idxs;
    get_selection_indexes(obj_idxs, vol_idxs);
    if (obj_idxs.empty() && vol_idxs.empty())
        return;

    auto can_append = [this, obj_idxs, vol_idxs](bool from_imperial_unit) {
        ModelObjectPtrs objects;
        for (int obj_idx : obj_idxs) {
            ModelObject* object = (*m_objects)[obj_idx];
            if (vol_idxs.empty()) {
                for (ModelVolume* volume : object->volumes)
                    if (volume->source.is_converted_from_inches == from_imperial_unit)
                        return false;
            }
            else {
                for (int vol_idx : vol_idxs)
                    if (object->volumes[vol_idx]->source.is_converted_from_inches == from_imperial_unit)
                        return false;
            }
        }
        return true;
    };

    wxString convert_menu_name = _L("Convert from imperial units");
    int      convert_menu_id   = menu->FindItem(convert_menu_name);
    wxString revert_menu_name  = _L("Revert conversion from imperial units");
    int      revert_menu_id    = menu->FindItem(revert_menu_name);

    if (can_append(true)) {
        // Delete revert menu item
        if (revert_menu_id != wxNOT_FOUND)
            menu->Destroy(revert_menu_id);
        // Add convert menu item if it doesn't exist
        if (convert_menu_id == wxNOT_FOUND)
            append_menu_item(menu, wxID_ANY, convert_menu_name, convert_menu_name,
                [](wxCommandEvent&) { wxGetApp().plater()->convert_unit(true); }, "", menu, 
                []() {return true;}, nullptr, insert_pos);
    }

    if (can_append(false)) {
        // Delete convert menu item
        if (convert_menu_id != wxNOT_FOUND)
            menu->Destroy(convert_menu_id);
        // Add convert menu item if it doesn't exist
        if (revert_menu_id == wxNOT_FOUND)
            append_menu_item(menu, wxID_ANY, revert_menu_name, revert_menu_name,
                [](wxCommandEvent&) { wxGetApp().plater()->convert_unit(false); }, "", menu,
                []() {return true;}, nullptr, insert_pos);
    }
}

void ObjectList::append_menu_item_merge_to_multipart_object(wxMenu* menu)
{
    menu->AppendSeparator();
    append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one multipart object"),
        [this](wxCommandEvent&) { merge(true); }, "", menu,
        [this]() { return this->can_merge_to_multipart_object(); }, wxGetApp().plater());
}

void ObjectList::append_menu_item_merge_to_single_object(wxMenu* menu)
{
    menu->AppendSeparator();
    append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one single object"),
        [this](wxCommandEvent&) { merge(false); }, "", menu,
        [this]() { return this->can_merge_to_single_object(); }, wxGetApp().plater());
}

void ObjectList::create_object_popupmenu(wxMenu *menu)
{
#ifdef __WXOSX__  
    append_menu_items_osx(menu);
#endif // __WXOSX__

    append_menu_item_reload_from_disk(menu);
    append_menu_item_export_stl(menu);
    append_menu_item_fix_through_netfabb(menu);
    append_menu_item_scale_selection_to_fit_print_volume(menu);

    // Split object to parts
    append_menu_item_split(menu);
//    menu->AppendSeparator();

    // Merge multipart object to the single object
//    append_menu_item_merge_to_single_object(menu);
    menu->AppendSeparator();

    // Layers Editing for object
    append_menu_item_layers_editing(menu, wxGetApp().plater());
    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)"
}

void ObjectList::create_sla_object_popupmenu(wxMenu *menu)
{
#ifdef __WXOSX__  
    append_menu_items_osx(menu);
#endif // __WXOSX__

    append_menu_item_reload_from_disk(menu);
    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_reload_from_disk(menu);
    append_menu_item_export_stl(menu);
    append_menu_item_fix_through_netfabb(menu);

    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)"
}

void ObjectList::create_instance_popupmenu(wxMenu*menu)
{
    m_menu_item_split_instances = append_menu_item_instance_to_object(menu, wxGetApp().plater());
}

void ObjectList::create_default_popupmenu(wxMenu*menu)
{
    wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID);
    append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part", 
        [](){return true;}, this);
}

wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
{
    wxMenu *menu = new wxMenu;

    settings_menu_hierarchy settings_menu;

    /* If we try to add settings for object/part from 3Dscene, 
     * for the second try there is selected ItemSettings in ObjectList.
     * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes
     */
    const wxDataViewItem selected_item = GetSelection();
    wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item;

    get_options_menu(settings_menu, !is_object_item(m_objects_model->GetItemType(item)));

    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,
                        [this]() { return true; }, wxGetApp().plater());
    }

    return menu;
}

void ObjectList::create_freq_settings_popupmenu(wxMenu *menu, const bool is_object_settings/* = true*/)
{
    // Add default settings bundles
    const SettingsBundle& bundle = printer_technology() == ptFFF ?
                                     FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA;

    const int extruders_cnt = extruders_count();

    for (auto& it : bundle) {
        if (improper_category(it.first, extruders_cnt, is_object_settings)) 
            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,
                        [this]() { return true; }, wxGetApp().plater());
    }
#if 0
    // Add "Quick" settings bundles
    const SettingsBundle& bundle_quick = printer_technology() == ptFFF ?
                                             m_freq_settings_fff : m_freq_settings_sla;

    for (auto& it : bundle_quick) {
        if (improper_category(it.first, extruders_cnt))
            continue;

        append_menu_item(menu, wxID_ANY, from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()), "",
                        [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,
                        [this]() { return true; }, wxGetApp().plater());
    }
#endif
}

void ObjectList::update_opt_keys(t_config_option_keys& opt_keys, const bool is_object)
{
    auto full_current_opts = get_options(!is_object);
    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)
{
    wxDataViewItem item = GetSelection();
    // we can add volumes for Object or Instance
    if (!item || !(m_objects_model->GetItemType(item)&(itObject|itInstance)))
        return;
    const int obj_idx = m_objects_model->GetObjectIdByItem(item);

    if (obj_idx < 0) return;

    // Get object item, if Instance is selected
    if (m_objects_model->GetItemType(item)&itInstance)
        item = m_objects_model->GetItemById(obj_idx);

    take_snapshot(_(L("Load Part")));

    std::vector<std::pair<wxString, bool>> volumes_info;
    load_part((*m_objects)[obj_idx], volumes_info, type);


    changed_object(obj_idx);
    if (type == ModelVolumeType::MODEL_PART)
        // update printable state on canvas
        wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)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);

//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
    selection_changed();
//#endif //no __WXOSX__ //__WXMSW__
}

void ObjectList::load_part( ModelObject* model_object,
                            std::vector<std::pair<wxString, bool>> &volumes_info,
                            ModelVolumeType type)
{
    wxWindow* parent = wxGetApp().tab_panel()->GetPage(0);

    wxArrayString input_files;
    wxGetApp().import_model(parent, input_files);
    for (size_t 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) {
                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));
            }
        }
    }

}

static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf3& bb)
{
    TriangleMesh mesh;

    const 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(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5);
    mesh.repair();

    return mesh;
}

void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type)
{
    if (type == ModelVolumeType::INVALID) {
        load_shape_object(type_name);
        return;
    }

    const int obj_idx = get_selected_obj_idx();
    if (obj_idx < 0) 
        return;

    const Selection& selection = scene_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;

    take_snapshot(_(L("Add Generic Subobject")));

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

    TriangleMesh mesh = create_mesh(type_name, instance_bb);
    
	// Mesh will be centered when loading.
    ModelVolume *new_volume = model_object.add_volume(std::move(mesh));
    new_volume->set_type(type);

    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(Geometry::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);
    }

    const wxString name = _(L("Generic")) + "-" + _(type_name);
    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);
    if (type == ModelVolumeType::MODEL_PART)
        // update printable state on canvas
        wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx);

    const auto object_item = m_objects_model->GetTopParent(GetSelection());
    select_item([this, object_item, name, type, new_volume]() { return 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::load_shape_object(const std::string& type_name)
{
    const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
    assert(selection.get_object_idx() == -1); // Add nothing is something is selected on 3DScene
    if (selection.get_object_idx() != -1)
        return;

    const int obj_idx = m_objects->size();
    if (obj_idx < 0)
        return;

    take_snapshot(_(L("Add Shape")));

    // Create mesh
    BoundingBoxf3 bb;
    TriangleMesh mesh = create_mesh(type_name, bb);
    load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name));
}

void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center)
{   
    // Add mesh to model as a new object
    Model& model = wxGetApp().plater()->model();

#ifdef _DEBUG
    check_model_ids_validity(model);
#endif /* _DEBUG */
    
    std::vector<size_t> object_idxs;
    auto bb = mesh.bounding_box();
    ModelObject* new_object = model.add_object();
    new_object->name = into_u8(name);
    new_object->add_instance(); // each object should have at list one instance
    
    ModelVolume* new_volume = new_object->add_volume(mesh);
    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));
    new_object->invalidate_bounding_box();
    new_object->translate(-bb.center());

    if (center) {
        const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
        new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
    } else {
        new_object->instances[0]->set_offset(bb.center());
    }

    new_object->ensure_on_bed();

    object_idxs.push_back(model.objects.size() - 1);
#ifdef _DEBUG
    check_model_ids_validity(model);
#endif /* _DEBUG */
    
    paste_objects_into_list(object_idxs);

#ifdef _DEBUG
    check_model_ids_validity(model);
#endif /* _DEBUG */
}

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(m_objects_model->GetParent(item));
    else if (type & itInstanceRoot && obj_idx != -1)
        del_instances_from_object(obj_idx);
    else if (type & itLayerRoot && obj_idx != -1)
        del_layers_from_object(obj_idx);
    else if (type & itLayer && obj_idx != -1)
        del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item));
    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(const wxDataViewItem& parent_item)
{
    const bool is_layer_settings = m_objects_model->GetItemType(parent_item) == itLayer;

    const size_t opt_cnt = m_config->keys().size();
    if ((opt_cnt == 1 && m_config->has("extruder")) ||
        (is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height")))
        return;

    take_snapshot(_(L("Delete Settings")));

    int extruder = m_config->has("extruder") ? m_config->extruder() : -1;

    coordf_t layer_height = 0.0;
    if (is_layer_settings)
        layer_height = m_config->opt_float("layer_height");

    m_config->clear();

    if (extruder >= 0)
        m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
    if (is_layer_settings)
        m_config->set_key_value("layer_height", new ConfigOptionFloat(layer_height));

    changed_object();
}

void ObjectList::del_instances_from_object(const int obj_idx)
{
    auto& instances = (*m_objects)[obj_idx]->instances;
    if (instances.size() <= 1)
        return;

    take_snapshot(_(L("Delete All Instances from Object")));

    while ( instances.size()> 1)
        instances.pop_back();

    (*m_objects)[obj_idx]->invalidate_bounding_box(); // ? #ys_FIXME

    changed_object(obj_idx);
}

void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range)
{
    const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range);
    if (del_range == object(obj_idx)->layer_config_ranges.end())
        return;

    take_snapshot(_(L("Delete Height Range")));
        
    object(obj_idx)->layer_config_ranges.erase(del_range);

    changed_object(obj_idx);
}

void ObjectList::del_layers_from_object(const int obj_idx)
{
    object(obj_idx)->layer_config_ranges.clear();

    changed_object(obj_idx);
}

bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type)
{
    assert(idx >= 0);
	if (obj_idx == 1000 || idx<0)
		// Cannot delete a wipe tower or volume with negative id
		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("From Object List You can't delete the last solid part from object.")));
            return false;
        }

        take_snapshot(_(L("Delete Subobject")));

        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("Last instance of an object cannot be deleted.")));
            return false;
        }

        take_snapshot(_(L("Delete Instance")));
        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<const ConfigOptionFloats*>(nozzle_dmtrs_opt)->values.size();
    if (!volume->is_splittable()) {
        wxMessageBox(_(L("The selected object couldn't be split because it contains only one part.")));
        return;
    }

    take_snapshot(_(L("Split to Parts")));

    volume->split(nozzle_dmrs_cnt);

    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.extruder() : 0,
            false);
        // add settings to the part, if it has those
        add_settings_item(vol_item, &volume->config.get());
    }

    model_object->input_file.clear();

    if (parent == item)
        Expand(parent);

    changed_object(obj_idx);
}

void ObjectList::merge(bool to_multipart_object)
{
    // merge selected objects to the multipart object
    if (to_multipart_object)
    {
        auto get_object_idxs = [this](std::vector<int>& obj_idxs, wxDataViewItemArray& sels)
        {
            // check selections and split instances to the separated objects...
            bool instance_selection = false;
            for (wxDataViewItem item : sels)
                if (m_objects_model->GetItemType(item) & itInstance) {
                    instance_selection = true;
                    break;
                }

            if (!instance_selection)
            {
                for (wxDataViewItem item : sels) {
                    assert(m_objects_model->GetItemType(item) & itObject);
                    obj_idxs.emplace_back(m_objects_model->GetIdByItem(item));
                }
                return;
            }

            // map of obj_idx -> set of selected instance_idxs
            std::map<int, std::set<int>> sel_map;
            std::set<int> empty_set;
            for (wxDataViewItem item : sels) {
                if (m_objects_model->GetItemType(item) & itObject)
                {
                    int obj_idx = m_objects_model->GetIdByItem(item);
                    int inst_cnt = (*m_objects)[obj_idx]->instances.size();
                    if (inst_cnt == 1)
                        sel_map.emplace(obj_idx, empty_set);
                    else
                        for (int i = 0; i < inst_cnt; i++)
                            sel_map[obj_idx].emplace(i);
                    continue;
                }
                int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
                sel_map[obj_idx].emplace(m_objects_model->GetInstanceIdByItem(item));
            }

            // all objects, created from the instances will be added to the end of list
            int new_objects_cnt = 0; // count of this new objects

            for (auto map_item : sel_map)
            {
                int obj_idx = map_item.first;
                // object with just 1 instance
                if (map_item.second.empty()) {
                    obj_idxs.emplace_back(obj_idx);
                    continue;
                }

                // object with selected all instances
                if ((*m_objects)[map_item.first]->instances.size() == map_item.second.size()) {
                    instances_to_separated_objects(obj_idx);
                    // first instance stay on its own place and another all add to the end of list :
                    obj_idxs.emplace_back(obj_idx);
                    new_objects_cnt += map_item.second.size() - 1;
                    continue;
                }

                // object with selected some of instances 
                instances_to_separated_object(obj_idx, map_item.second);

                if (map_item.second.size() == 1)
                    new_objects_cnt += 1;
                else {// we should split to separate instances last object
                    instances_to_separated_objects(m_objects->size() - 1);
                    // all instances will stay at the end of list :
                    new_objects_cnt += map_item.second.size();
                }
            }

            // all instatnces are extracted to the separate objects and should be selected
            m_prevent_list_events = true;
            sels.Clear();
            for (int obj_idx : obj_idxs)
                sels.Add(m_objects_model->GetItemById(obj_idx));
            int obj_cnt = m_objects->size();
            for (int obj_idx = obj_cnt - new_objects_cnt; obj_idx < obj_cnt; obj_idx++) {
                sels.Add(m_objects_model->GetItemById(obj_idx));
                obj_idxs.emplace_back(obj_idx);
            }
            UnselectAll();
            SetSelections(sels);
            assert(!sels.IsEmpty());
            m_prevent_list_events = false;
        };

        std::vector<int> obj_idxs;
        wxDataViewItemArray sels;
        GetSelections(sels);
        assert(!sels.IsEmpty());

        Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Merge"));

        get_object_idxs(obj_idxs, sels);

        // resulted objects merge to the one
        Model* model = (*m_objects)[0]->get_model();
        ModelObject* new_object = model->add_object();
        new_object->name = _u8L("Merged");
        ModelConfig &config = new_object->config;

        for (int obj_idx : obj_idxs)
        {
            ModelObject* object = (*m_objects)[obj_idx];

            const Geometry::Transformation& transformation = object->instances[0]->get_transformation();
            Vec3d scale     = transformation.get_scaling_factor();
            Vec3d mirror    = transformation.get_mirror();
            Vec3d rotation  = transformation.get_rotation();

            if (object->id() == (*m_objects)[obj_idxs.front()]->id())
                new_object->add_instance();
            Transform3d     volume_offset_correction = new_object->instances[0]->get_transformation().get_matrix().inverse() * transformation.get_matrix();

            // merge volumes
            for (const ModelVolume* volume : object->volumes) {
                ModelVolume* new_volume = new_object->add_volume(*volume);

                //set rotation
                Vec3d vol_rot = new_volume->get_rotation() + rotation;
                new_volume->set_rotation(vol_rot);

                // set scale
                Vec3d vol_sc_fact = new_volume->get_scaling_factor().cwiseProduct(scale);
                new_volume->set_scaling_factor(vol_sc_fact);

                // set mirror
                Vec3d vol_mirror = new_volume->get_mirror().cwiseProduct(mirror);
                new_volume->set_mirror(vol_mirror);

                // set offset
                Vec3d vol_offset = volume_offset_correction* new_volume->get_offset();
                new_volume->set_offset(vol_offset);
            }

            // merge settings
            auto new_opt_keys = config.keys();
            const ModelConfig& from_config = object->config;
            auto opt_keys = from_config.keys();

            for (auto& opt_key : opt_keys) {
                if (find(new_opt_keys.begin(), new_opt_keys.end(), opt_key) == new_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);
                    }
                    config.set_key_value(opt_key, option->clone());
                }
            }
            // save extruder value if it was set
            if (object->volumes.size() == 1 && find(opt_keys.begin(), opt_keys.end(), "extruder") != opt_keys.end()) {
                ModelVolume* volume = new_object->volumes.back();
                const ConfigOption* option = from_config.option("extruder");
                if (option)
                    volume->config.set_key_value("extruder", option->clone());
            }

            // merge layers
            for (const auto& range : object->layer_config_ranges)
                new_object->layer_config_ranges.emplace(range);
        }
        // remove selected objects
        remove();

        // Add new object(merged) to the object_list
        add_object_to_list(m_objects->size() - 1);
        select_item(m_objects_model->GetItemById(m_objects->size() - 1));
        update_selections_on_canvas();
    }
    // merge all parts to the one single object
    // all part's settings will be lost
    else
    {
        wxDataViewItem item = GetSelection();
        if (!item)
            return;
        const int obj_idx = m_objects_model->GetIdByItem(item);
        if (obj_idx == -1)
            return;

        Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Merge all parts to the one single object"));

        ModelObject* model_object = (*m_objects)[obj_idx];
        model_object->merge();

        m_objects_model->DeleteVolumeChildren(item);

        changed_object(obj_idx);
    }
}

void ObjectList::layers_editing()
{
    const Selection& selection = scene_selection();
    const int obj_idx = selection.get_object_idx();
    wxDataViewItem item = obj_idx >= 0 && GetSelectedItemsCount() > 1 && selection.is_single_full_object() ? 
                          m_objects_model->GetItemById(obj_idx) :
                          GetSelection();

    if (!item)
        return;

    const wxDataViewItem obj_item = m_objects_model->GetTopParent(item);
    wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(obj_item);

    // if it doesn't exist now
    if (!layers_item.IsOk())
    {
        t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;

        // set some default value
        if (ranges.empty()) {
            take_snapshot(_(L("Add Layers")));
            ranges[{ 0.0f, 2.0f }].assign_config(get_default_layer_config(obj_idx));
        }

        // create layer root item
        layers_item = add_layer_root_item(obj_item);
    }
    if (!layers_item.IsOk())
        return;

    // to correct visual hints for layers editing on the Scene, reset previous selection
    wxGetApp().obj_layers()->reset_selection();
    wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);

    // select LayerRoor item and expand
    select_item(layers_item);
    Expand(layers_item);
}

wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item)
{
    const int obj_idx = m_objects_model->GetIdByItem(obj_item);
    if (obj_idx < 0 || 
        object(obj_idx)->layer_config_ranges.empty() ||
        printer_technology() == ptSLA)
        return wxDataViewItem(nullptr);

    // create LayerRoot item
    wxDataViewItem layers_item = m_objects_model->AddLayersRoot(obj_item);

    // and create Layer item(s) according to the layer_config_ranges
    for (const auto& range : object(obj_idx)->layer_config_ranges)
        add_layer_item(range.first, layers_item);

    Expand(layers_item);
    return layers_item;
}

DynamicPrintConfig ObjectList::get_default_layer_config(const int obj_idx)
{
    DynamicPrintConfig config;
    coordf_t layer_height = object(obj_idx)->config.has("layer_height") ? 
                            object(obj_idx)->config.opt_float("layer_height") : 
                            wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("layer_height");
    config.set_key_value("layer_height",new ConfigOptionFloat(layer_height));
    config.set_key_value("extruder",    new ConfigOptionInt(0));

    return config;
}

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 = scene_selection();
    return selection.is_multiple_full_instance() || selection.is_single_full_instance();
}

bool ObjectList::can_merge_to_multipart_object() const
{
    if (printer_technology() == ptSLA)
        return false;

    wxDataViewItemArray sels;
    GetSelections(sels);
    if (sels.IsEmpty())
        return false;

    // should be selected just objects
    for (wxDataViewItem item : sels)
        if (!(m_objects_model->GetItemType(item) & (itObject | itInstance)))
            return false;

    return true;
}

bool ObjectList::can_merge_to_single_object() const
{
    int obj_idx = get_selected_obj_idx();
    if (obj_idx < 0)
        return false;

    // selected object should be multipart
    return (*m_objects)[obj_idx]->volumes.size() > 1;
}

// 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()
{
    if (m_extruder_editor) m_extruder_editor->Hide();
    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;
    bool update_and_show_layers = false;

    const auto item = GetSelection();

    if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot ))
    {
        og_name = _(L("Group manipulation"));

        const Selection& selection = scene_selection();
        // don't show manipulation panel for case of all Object's parts selection 
        update_and_show_manipulations = !selection.is_single_full_instance();
    }
    else
    {
        if (item)
        {
            if (m_objects_model->GetParent(item) == wxDataViewItem(nullptr)) {
                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 {
                obj_idx = m_objects_model->GetObjectIdByItem(item);
                
                const ItemType type = m_objects_model->GetItemType(item);
                if (type & itSettings) {
                    const auto parent = m_objects_model->GetParent(item);
                    const ItemType parent_type = m_objects_model->GetItemType(parent);

                    if (parent_type & itObject) {
                        og_name = _(L("Object Settings to modify"));
                        m_config = &(*m_objects)[obj_idx]->config;
                    }
                    else if (parent_type & itVolume) {
                        og_name = _(L("Part Settings to modify"));
                        volume_id = m_objects_model->GetVolumeIdByItem(parent);
                        m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config;
                    }
                    else if (parent_type & itLayer) {
                        og_name = _(L("Layer range Settings to modify"));
                        m_config = &get_item_config(parent);
                    }
                    update_and_show_settings = true;
                }
                else if (type & 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 (type & itInstance) {
                    og_name = _(L("Instance manipulation"));
                    update_and_show_manipulations = true;

                    // fill m_config by object's values
                    m_config = &(*m_objects)[obj_idx]->config;
                }
                else if (type & (itLayerRoot|itLayer)) {
                    og_name = type & itLayerRoot ? _(L("Height ranges")) : _(L("Settings for height range"));
                    update_and_show_layers = true;

                    if (type & itLayer)
                        m_config = &get_item_config(item);
                }
            }
        }
    }

    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_item_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 + " ");

    if (printer_technology() == ptSLA)
        update_and_show_layers = false;
    else if (update_and_show_layers)
        wxGetApp().obj_layers()->get_og()->set_name(" " + og_name + " ");

    Sidebar& panel = wxGetApp().sidebar();
    panel.Freeze();

    wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
    wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations);
    wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings);
    wxGetApp().obj_layers()  ->UpdateAndShow(update_and_show_layers);
    wxGetApp().sidebar().show_info_sizer();

    panel.Layout();
    panel.Thaw();
}

SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings)
{
    auto opt_keys = config->keys();
    if (opt_keys.empty())
        return SettingsBundle();

    update_opt_keys(opt_keys, is_object_settings); // update options list according to print technology

    if (opt_keys.empty())
        return SettingsBundle();

    const int extruders_cnt = wxGetApp().extruders_edited_cnt();

    SettingsBundle bundle;
    for (auto& opt_key : opt_keys)
    {
        auto category = config->def()->get(opt_key)->category;
        if (improper_category(category, extruders_cnt, is_object_settings))
            continue;

        std::vector< std::string > new_category;

        auto& cat_opt = bundle.find(category) == bundle.end() ? new_category : bundle.at(category);
        cat_opt.push_back(opt_key);
        if (cat_opt.size() == 1)
            bundle[category] = cat_opt;
    }

    return bundle;
}

// Add new SettingsItem for parent_item if it doesn't exist, or just update a digest according to new config
wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config)
{
    wxDataViewItem ret = wxDataViewItem(nullptr);

    if (!parent_item)
        return ret;

    const bool is_object_settings = m_objects_model->GetItemType(parent_item) == itObject;
    SettingsBundle cat_options = get_item_settings_bundle(config, is_object_settings);
    if (cat_options.empty())
        return ret;

    std::vector<std::string> categories;
    categories.reserve(cat_options.size());
    for (auto& cat : cat_options)
        categories.push_back(cat.first);

    if (m_objects_model->GetItemType(parent_item) & itInstance)
        parent_item = m_objects_model->GetTopParent(parent_item);

    ret = m_objects_model->IsSettingsItem(parent_item) ? parent_item : m_objects_model->GetSettingsItem(parent_item);

    if (!ret) ret = m_objects_model->AddSettingsChild(parent_item);

    m_objects_model->UpdateSettingsDigest(ret, categories);
    Expand(parent_item);

    return ret;
}

void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
{
    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") ? model_object->config.extruder() : 0,
                      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") ? volume->config.extruder() : 0,
                false);
            add_settings_item(vol_item, &volume->config.get());
        }
        Expand(item);
    }

    // add instances to the object, if it has those
    if (model_object->instances.size()>1)
    {
        std::vector<bool> print_idicator(model_object->instances.size());
        for (size_t i = 0; i < model_object->instances.size(); ++i)
            print_idicator[i] = model_object->instances[i]->printable;

        const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx);
        m_objects_model->AddInstanceChild(object_item, print_idicator);
        Expand(m_objects_model->GetInstanceRootItem(object_item));
    }
    else
        m_objects_model->SetPrintableState(model_object->instances[0]->printable ? piPrintable : piUnprintable, obj_idx);

    // add settings to the object, if it has those
    add_settings_item(item, &model_object->config.get());

    // Add layers if it has
    add_layer_root_item(item);

#ifndef __WXOSX__ 
    if (call_selection_changed)
	    selection_changed();
#endif //__WXMSW__
}

void ObjectList::delete_object_from_list()
{
    auto item = GetSelection();
    if (!item) 
        return;
    if (m_objects_model->GetParent(item) == wxDataViewItem(nullptr))
        select_item([this, item]() { return m_objects_model->Delete(item); });
    else
        select_item([this, item]() { return m_objects_model->Delete(m_objects_model->GetParent(item)); });
}

void ObjectList::delete_object_from_list(const size_t obj_idx)
{
    select_item([this, obj_idx]() { return 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([this, obj_idx, vol_idx]() { return 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([this, obj_idx, inst_idx]() { return 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;

    take_snapshot(_(L("Delete Selected Item")));

    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<ItemForDelete>& items_for_delete)
{
    if (items_for_delete.empty())
        return;

    m_prevent_list_events = true;
    for (std::vector<ItemForDelete>::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.extruder());
                    m_objects_model->SetExtruder(extruder, m_objects_model->GetItemById(item->obj_idx));
                }
                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));
        }
    }
    m_prevent_list_events = true;
    part_selection_changed();
}

void ObjectList::delete_all_objects_from_list()
{
    m_prevent_list_events = true;
    this->UnselectAll();
    m_objects_model->DeleteAll();
    m_prevent_list_events = false;
    part_selection_changed();
}

void ObjectList::increase_object_instances(const size_t obj_idx, const size_t num)
{
    select_item([this, obj_idx, num]() { return m_objects_model->AddInstanceChild(m_objects_model->GetItemById(obj_idx), num); });
    selection_changed();
}

void ObjectList::decrease_object_instances(const size_t obj_idx, const size_t num)
{
    select_item([this, obj_idx, num]() { return 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;
}

static void update_selection(wxDataViewItemArray& sels, ObjectList::SELECTION_MODE mode, ObjectDataViewModel* model)
{
    if (mode == ObjectList::smInstance)
    {
        for (auto& item : sels)
        {
            ItemType type = model->GetItemType(item);
            if (type == itObject)
                continue;
            if (type == itInstanceRoot) {
                wxDataViewItem obj_item = model->GetParent(item);
                sels.Remove(item);
                sels.Add(obj_item);
                update_selection(sels, mode, model);
                return;
            }
            if (type == itInstance)
            {
                wxDataViewItemArray instances;
                model->GetChildren(model->GetParent(item), instances);
                assert(instances.Count() > 0);
                size_t selected_instances_cnt = 0;
                for (auto& inst : instances) {
                    if (sels.Index(inst) == wxNOT_FOUND)
                        break;
                    selected_instances_cnt++;
                }

                if (selected_instances_cnt == instances.Count()) 
                {
                    wxDataViewItem obj_item = model->GetTopParent(item);
                    for (auto& inst : instances)
                        sels.Remove(inst);
                    sels.Add(obj_item);
                    update_selection(sels, mode, model);
                    return;
                }
            }
            else
                return;
        }
    }
}

void ObjectList::remove()
{
    if (GetSelectedItemsCount() == 0)
        return;

    auto delete_item = [this](wxDataViewItem item)
    {
        wxDataViewItem parent = m_objects_model->GetParent(item);
        ItemType type = m_objects_model->GetItemType(item);
        if (type & itObject)
            delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1);
        else {
            if (type & (itLayer | itInstance)) {
                // In case there is just one layer or two instances and we delete it, del_subobject_item will
                // also remove the parent item. Selection should therefore pass to the top parent (object).
                wxDataViewItemArray children;
                if (m_objects_model->GetChildren(parent, children) == (type & itLayer ? 1 : 2))
                    parent = m_objects_model->GetTopParent(item);
            }

            del_subobject_item(item);
        }

        return parent;
    };

    wxDataViewItemArray sels;
    GetSelections(sels);

    wxDataViewItem parent = wxDataViewItem(nullptr);

    if (sels.Count() == 1)
        parent = delete_item(GetSelection());
    else
    {
        SELECTION_MODE sels_mode = m_selection_mode;
        UnselectAll();
        update_selection(sels, sels_mode, m_objects_model);

        Plater::TakeSnapshot snapshot = Plater::TakeSnapshot(wxGetApp().plater(), _(L("Delete Selected")));

        for (auto& item : sels)
        {
            if (m_objects_model->InvalidItem(item)) // item can be deleted for this moment (like last 2 Instances or Volumes)
                continue;
            parent = delete_item(item);
        }
    }

    if (parent && !m_objects_model->InvalidItem(parent)) {
        select_item(parent);
        update_selections_on_canvas();
    }
}

void ObjectList::del_layer_range(const t_layer_height_range& range)
{
    const int obj_idx = get_selected_obj_idx();
    if (obj_idx < 0) return;

    t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;

    wxDataViewItem selectable_item = GetSelection();

    if (ranges.size() == 1)
        selectable_item = m_objects_model->GetParent(selectable_item);

    wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, range);
    del_subobject_item(layer_item);

    select_item(selectable_item);
}

static double get_min_layer_height(const int extruder_idx)
{
    const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
    return config.opt_float("min_layer_height", std::max(0, extruder_idx - 1));
}

static double get_max_layer_height(const int extruder_idx)
{
    const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
    int extruder_idx_zero_based = std::max(0, extruder_idx - 1);
    double max_layer_height = config.opt_float("max_layer_height", extruder_idx_zero_based);

    // In case max_layer_height is set to zero, it should default to 75 % of nozzle diameter:
    if (max_layer_height < EPSILON)
        max_layer_height = 0.75 * config.opt_float("nozzle_diameter", extruder_idx_zero_based);

    return max_layer_height;
}

// When editing this function, please synchronize the conditions with can_add_new_range_after_current().
void ObjectList::add_layer_range_after_current(const t_layer_height_range current_range)
{
    const int obj_idx = get_selected_obj_idx();
    assert(obj_idx >= 0);
    if (obj_idx < 0) 
        // This should not happen.
        return;

    const wxDataViewItem layers_item = GetSelection();

    auto& ranges = object(obj_idx)->layer_config_ranges;
    auto it_range = ranges.find(current_range);
    assert(it_range != ranges.end());
    if (it_range == ranges.end())
        // This shoudl not happen.
        return;

    auto it_next_range = it_range;
    bool changed = false;
    if (++ it_next_range == ranges.end())
    {
        // Adding a new layer height range after the last one.
        take_snapshot(_(L("Add Height Range")));
        changed = true;

        const t_layer_height_range new_range = { current_range.second, current_range.second + 2. };
        ranges[new_range].assign_config(get_default_layer_config(obj_idx));
        add_layer_item(new_range, layers_item);
    }
    else if (const std::pair<coordf_t, coordf_t> &next_range = it_next_range->first; current_range.second <= next_range.first)
    {
        const int layer_idx = m_objects_model->GetItemIdByLayerRange(obj_idx, next_range);
        assert(layer_idx >= 0);
        if (layer_idx >= 0) 
        {
            if (current_range.second == next_range.first)
            {
                // Splitting the next layer height range to two.
                const auto old_config = ranges.at(next_range);
                const coordf_t delta = next_range.second - next_range.first;
                // Layer height of the current layer.
                const coordf_t old_min_layer_height = get_min_layer_height(old_config.opt_int("extruder"));
                // Layer height of the layer to be inserted.
                const coordf_t new_min_layer_height = get_min_layer_height(0);
                if (delta >= old_min_layer_height + new_min_layer_height - EPSILON) {
                    const coordf_t middle_layer_z = (new_min_layer_height > 0.5 * delta) ?
	                    next_range.second - new_min_layer_height :
                    	next_range.first + std::max(old_min_layer_height, 0.5 * delta);
                    t_layer_height_range new_range = { middle_layer_z, next_range.second };

                    Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add Height Range")));
                    changed = true;

                    // create new 2 layers instead of deleted one
                    // delete old layer

                    wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range);
                    del_subobject_item(layer_item);

                    ranges[new_range] = old_config;
                    add_layer_item(new_range, layers_item, layer_idx);

                    new_range = { current_range.second, middle_layer_z };
                    ranges[new_range].assign_config(get_default_layer_config(obj_idx));
                    add_layer_item(new_range, layers_item, layer_idx);
                }
            }
            else if (next_range.first - current_range.second >= get_min_layer_height(0) - EPSILON)
            {
                // Filling in a gap between the current and a new layer height range with a new one.
                take_snapshot(_(L("Add Height Range")));
                changed = true;

                const t_layer_height_range new_range = { current_range.second, next_range.first };
                ranges[new_range].assign_config(get_default_layer_config(obj_idx));
                add_layer_item(new_range, layers_item, layer_idx);
            }
        }
    }

    if (changed)
        changed_object(obj_idx);

    // The layer range panel is updated even if this function does not change the layer ranges, as the panel update
    // may have been postponed from the "kill focus" event of a text field, if the focus was lost for the "add layer" button.
    // select item to update layers sizer
    select_item(layers_item);
}

// Returning an empty string means that the layer could be added after the current layer.
// Otherwise an error tooltip is returned.
// When editing this function, please synchronize the conditions with add_layer_range_after_current().
wxString ObjectList::can_add_new_range_after_current(const t_layer_height_range current_range)
{
    const int obj_idx = get_selected_obj_idx();
    assert(obj_idx >= 0);
    if (obj_idx < 0)
        // This should not happen.
        return "ObjectList assert";

    auto& ranges = object(obj_idx)->layer_config_ranges;
    auto it_range = ranges.find(current_range);
    assert(it_range != ranges.end());
    if (it_range == ranges.end())
        // This shoudl not happen.
        return "ObjectList assert";

    auto it_next_range = it_range;
    if (++ it_next_range == ranges.end())
    	// Adding a layer after the last layer is always possible.
        return "";
    
    if (const std::pair<coordf_t, coordf_t>& next_range = it_next_range->first; current_range.second <= next_range.first)
    {
        if (current_range.second == next_range.first) {
            if (next_range.second - next_range.first < get_min_layer_height(it_next_range->second.opt_int("extruder")) + get_min_layer_height(0) - EPSILON)
                return _(L("Cannot insert a new layer range after the current layer range.\n"
                	       "The next layer range is too thin to be split to two\n"
                	       "without violating the minimum layer height."));
        } else if (next_range.first - current_range.second < get_min_layer_height(0) - EPSILON) {
            return _(L("Cannot insert a new layer range between the current and the next layer range.\n"
            	       "The gap between the current layer range and the next layer range\n"
            	       "is thinner than the minimum layer height allowed."));
        }
    } else
	    return _(L("Cannot insert a new layer range after the current layer range.\n"
	    		   "Current layer range overlaps with the next layer range."));

	// All right, new layer height range could be inserted.
	return "";
}

void ObjectList::add_layer_item(const t_layer_height_range& range, 
                                const wxDataViewItem layers_item, 
                                const int layer_idx /* = -1*/)
{
    const int obj_idx = m_objects_model->GetObjectIdByItem(layers_item);
    if (obj_idx < 0) return;

    const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range].get();
    if (!config.has("extruder"))
        return;

    const auto layer_item = m_objects_model->AddLayersChild(layers_item, 
                                                            range, 
                                                            config.opt_int("extruder"),
                                                            layer_idx);
    add_settings_item(layer_item, &config);
}

bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height)
{
    // Use m_selected_object_id instead of get_selected_obj_idx()
    // because of get_selected_obj_idx() return obj_idx for currently selected item.
    // But edit_layer_range(...) function can be called, when Selection in ObjectList could be changed
    const int obj_idx = m_selected_object_id ; 
    if (obj_idx < 0) 
        return false;

    ModelConfig* config = &object(obj_idx)->layer_config_ranges[range];
    if (fabs(layer_height - config->opt_float("layer_height")) < EPSILON)
        return false;

    const int extruder_idx = config->opt_int("extruder");

    if (layer_height >= get_min_layer_height(extruder_idx) && 
        layer_height <= get_max_layer_height(extruder_idx)) 
    {
        config->set_key_value("layer_height", new ConfigOptionFloat(layer_height));
        changed_object(obj_idx);
        return true;
    }

    return false;
}

bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range, bool dont_update_ui)
{
    // Use m_selected_object_id instead of get_selected_obj_idx()
    // because of get_selected_obj_idx() return obj_idx for currently selected item.
    // But edit_layer_range(...) function can be called, when Selection in ObjectList could be changed
    const int obj_idx = m_selected_object_id;
    if (obj_idx < 0) return false;

    take_snapshot(_(L("Edit Height Range")));

    const ItemType sel_type = m_objects_model->GetItemType(GetSelection());

    auto& ranges = object(obj_idx)->layer_config_ranges;

    {
        ModelConfig config = std::move(ranges[range]);
        ranges.erase(range);
        ranges[new_range] = std::move(config);
    }

    changed_object(obj_idx);
    
    wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx));
    // To avoid update selection after deleting of a selected item (under GTK)
    // set m_prevent_list_events to true
    m_prevent_list_events = true;
    m_objects_model->DeleteChildren(root_item);

    if (root_item.IsOk()) {
        // create Layer item(s) according to the layer_config_ranges
        for (const auto& r : ranges)
            add_layer_item(r.first, root_item);
    }

    // if this function was invoked from wxEVT_CHANGE_SELECTION selected item could be other than itLayer or itLayerRoot      
    if (!dont_update_ui && (sel_type & (itLayer | itLayerRoot)))
        select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item);

    Expand(root_item);

    m_prevent_list_events = false;
    return true;
}

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

int ObjectList::get_selected_layers_range_idx() const
{
    const wxDataViewItem& item = GetSelection();
    if (!item) 
        return -1;

    const ItemType type = m_objects_model->GetItemType(item);
    if (type & itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) != itLayer)
        return -1;

    return m_objects_model->GetLayerIdByItem(type & itLayer ? item : m_objects_model->GetParent(item));
}

void ObjectList::update_selections()
{
    const Selection& selection = scene_selection();
    wxDataViewItemArray sels;

    if ( ( m_selection_mode & (smSettings|smLayer|smLayerRoot) ) == 0)
        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 )
    if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer))
    {
        const auto item = GetSelection();
        if (selection.is_single_full_object()) {
            if (m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx())
                return;
            sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
        }
        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;
        }

        // but if there is selected only one of several instances by context menu,
        // then select this instance in ObjectList
        if (selection.is_single_full_instance())
            sels.Add(m_objects_model->GetItemByInstanceId(selection.get_object_idx(), selection.get_instance_idx()));
    }
    else if (selection.is_single_full_object() || selection.is_multiple_full_object())
    {
        const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content();
        // it's impossible to select Settings, Layer or LayerRoot for several objects
        if (!selection.is_multiple_full_object() && (m_selection_mode & (smSettings | smLayer | smLayerRoot)))
        {
            auto obj_idx = objects_content.begin()->first;
            wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx);
            if (m_selection_mode & smSettings)
            {
                if (m_selected_layers_range_idx < 0)
                    sels.Add(m_objects_model->GetSettingsItem(obj_item));
                else
                    sels.Add(m_objects_model->GetSettingsItem(m_objects_model->GetItemByLayerId(obj_idx, m_selected_layers_range_idx)));
            }
            else if (m_selection_mode & smLayerRoot)
                sels.Add(m_objects_model->GetLayerRootItem(obj_item));
            else if (m_selection_mode & smLayer) {
                if (m_selected_layers_range_idx >= 0)
                    sels.Add(m_objects_model->GetItemByLayerId(obj_idx, m_selected_layers_range_idx));
                else
                    sels.Add(obj_item);
            }
        }
        else {
        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())
    {
        if (m_selection_mode & smSettings)
        {
            const auto idx = *selection.get_volume_idxs().begin();
            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).
                wxDataViewItem vol_item = m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx());
                sels.Add(m_objects_model->GetSettingsItem(vol_item));
            }
        }
        else {
        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() &&
                        !selection.is_from_single_instance() ) // a case when volumes of different types are selected
                    {
                        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 & smSettings)
        m_selection_mode = smUndef;
    
    select_items(sels);

    // Scroll selected Item in the middle of an object list
    ensure_current_item_visible();
}

void ObjectList::update_selections_on_canvas()
{
    Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();

    const int sel_cnt = GetSelectedItemsCount();
    if (sel_cnt == 0) {
        selection.remove_all();
        wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state();
        return;
    }

    std::vector<unsigned int> volume_idxs;
    Selection::EMode mode = Selection::Volume;
    bool single_selection = sel_cnt == 1;
    auto add_to_selection = [this, &volume_idxs, &single_selection](const wxDataViewItem& item, const Selection& selection, int instance_idx, Selection::EMode& mode)
    {
        const ItemType& type = m_objects_model->GetItemType(item);
        const int obj_idx = m_objects_model->GetObjectIdByItem(item);

        if (type == itVolume) {
            const int vol_idx = m_objects_model->GetVolumeIdByItem(item);
            std::vector<unsigned int> idxs = selection.get_volume_idxs_from_volume(obj_idx, std::max(instance_idx, 0), vol_idx);
            volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
        }
        else if (type == itInstance) {
            const int inst_idx = m_objects_model->GetInstanceIdByItem(item);
            mode = Selection::Instance;
            std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx);
            volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
        }
        else
        {
            mode = Selection::Instance;
            single_selection &= (obj_idx != selection.get_object_idx());
            std::vector<unsigned int> idxs = selection.get_volume_idxs_from_object(obj_idx);
            volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
        }
    };

    // 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 | itLayerRoot | itLayer))
            add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode);
        else
            add_to_selection(item, selection, instance_idx, mode);
    }
    else
    {
        wxDataViewItemArray sels;
        GetSelections(sels);

        // clear selection before adding new elements 
        selection.clear(); //OR remove_all()? 

        for (auto item : sels)
        {
            add_to_selection(item, selection, instance_idx, mode);
        }
    }

    if (selection.contains_all_volumes(volume_idxs))
    {
        // remove
        volume_idxs = selection.get_missing_volume_idxs_from(volume_idxs);
        if (volume_idxs.size() > 0)
        {
            Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection-Remove from list")));
            selection.remove_volumes(mode, volume_idxs);
        }
    }
    else
    {
        // add
        volume_idxs = selection.get_unselected_volume_idxs_from(volume_idxs);
        Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection-Add from list")));
        selection.add_volumes(mode, volume_idxs, single_selection);
    }

    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_item(std::function<wxDataViewItem()> get_item)
{
    if (!get_item) 
        return;

    m_prevent_list_events = true;

    wxDataViewItem item = get_item();
    if (item.IsOk()) {
        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 (size_t i = 0; i < m_objects->size(); i++)
            sels.Add(m_objects_model->GetItemById(i));
        m_selection_mode = smInstance;
    }
    else {
        const auto item = GetSelection();
        const ItemType item_type = m_objects_model->GetItemType(item);
        // Some volume/layer/instance is selected    =>  select all volumes/layers/instances inside the current object
        if (item_type & (itVolume | itInstance | itLayer))
            m_objects_model->GetChildren(m_objects_model->GetParent(item), sels);

        m_selection_mode = item_type&itVolume ? smVolume : 
                           item_type&itLayer  ? smLayer  : smInstance;
    }

    SetSelections(sels);
    selection_changed();
}

// update selection mode for non-multiple selection
void ObjectList::update_selection_mode()
{
    m_selected_layers_range_idx=-1;
    // All items are unselected 
    if (!GetSelection())
    {
        m_last_selected_item = wxDataViewItem(nullptr);
        m_selection_mode = smUndef;
        return;
    }

    const ItemType type = m_objects_model->GetItemType(GetSelection());
    m_selection_mode =  type & itSettings ? smUndef  :
                        type & itLayer    ? smLayer  :
                        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 Volumes, Layers 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 same type items from different Objects
    auto impossible_multi_selection = [type, this](const ItemType item_type, const SELECTION_MODE selection_mode) {
        if (!(type & item_type && m_selection_mode & selection_mode))
            return false;

        wxDataViewItemArray sels;
        GetSelections(sels);
        for (const auto& sel : sels)
            if (sel != m_last_selected_item &&
                m_objects_model->GetTopParent(sel) != m_objects_model->GetTopParent(m_last_selected_item))
                return true;

        return false;
    };

    if (impossible_multi_selection(itVolume, smVolume) ||
        impossible_multi_selection(itLayer,  smLayer ) ||
        type & itSettings ||
        (type & itVolume   && !(m_selection_mode & smVolume  )) ||
        (type & itLayer    && !(m_selection_mode & smLayer   )) ||
        (type & itInstance && !(m_selection_mode & smInstance))
        )
    {
        // Inform user why selection isn't completed
        const wxString item_type = m_selection_mode & smInstance ? _(L("Object or Instance")) : 
                                   m_selection_mode & smVolume   ? _(L("Part")) : _(L("Layer"));

        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|smLayer))
    {
        // 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);

        const ItemType item_type = m_selection_mode & smVolume ? itVolume : itLayer;

        for (const auto& child : children)
            if (IsSelected(child) && m_objects_model->GetItemType(child) & item_type)
                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(nullptr) && 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(nullptr);

    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[] = { _(L("Part")), _(L("Modifier")), _(L("Support Enforcer")), _(L("Support Blocker")) };
    
    auto new_type = ModelVolumeType(wxGetSingleChoiceIndex(_(L("Type:")), _(L("Select type of part")), wxArrayString(4, names), int(type)));

	if (new_type == type || new_type == ModelVolumeType::INVALID)
        return;

    take_snapshot(_(L("Change Part Type")));

    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)) {
        add_settings_item(item, &volume->config.get());
    }
}

void ObjectList::last_volume_is_deleted(const int obj_idx)
{

    if (obj_idx < 0 || size_t(obj_idx) >= m_objects->size() || (*m_objects)[obj_idx]->volumes.size() != 1)
        return;

    auto volume = (*m_objects)[obj_idx]->volumes.front();

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

void ObjectList::update_and_show_object_settings_item()
{
    const wxDataViewItem item = GetSelection();
    if (!item) return;

    const wxDataViewItem& obj_item = m_objects_model->IsSettingsItem(item) ? m_objects_model->GetParent(item) : item;
    select_item([this, obj_item](){ return add_settings_item(obj_item, &get_item_config(obj_item).get()); });
}

// Update settings item for item had it
void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections)
{
    const wxDataViewItem old_settings_item = m_objects_model->GetSettingsItem(item);
    const wxDataViewItem new_settings_item = add_settings_item(item, &get_item_config(item).get());

    if (!new_settings_item && old_settings_item)
        m_objects_model->Delete(old_settings_item);

    // if ols settings item was is selected area
    if (selections.Index(old_settings_item) != wxNOT_FOUND)
    {
        // If settings item was just updated
        if (old_settings_item == new_settings_item)
        {
            Sidebar& panel = wxGetApp().sidebar();
            panel.Freeze();

            // update settings list
            wxGetApp().obj_settings()->UpdateAndShow(true);

            panel.Layout();
            panel.Thaw();
        }
        else
        // If settings item was deleted from the list, 
        // it's need to be deleted from selection array, if it was there
        {
            selections.Remove(old_settings_item);

            // Select item, if settings_item doesn't exist for item anymore, but was selected
            if (selections.Index(item) == wxNOT_FOUND) {
                selections.Add(item);
                select_item(item); // to correct update of the SettingsList and ManipulationPanel sizers
            }
        }
    }
}

void ObjectList::update_object_list_by_printer_technology()
{
    m_prevent_canvas_selection_update = true;
    wxDataViewItemArray sel;
    GetSelections(sel); // stash selection

    wxDataViewItemArray object_items;
    m_objects_model->GetChildren(wxDataViewItem(nullptr), object_items);

    for (auto& object_item : object_items) {
        // Update Settings Item for object
        update_settings_item_and_selection(object_item, sel);

        // Update settings for Volumes
        wxDataViewItemArray all_object_subitems;
        m_objects_model->GetChildren(object_item, all_object_subitems);
        for (auto item : all_object_subitems)
            if (m_objects_model->GetItemType(item) & itVolume)
                // update settings for volume
                update_settings_item_and_selection(item, sel);

        // Update Layers Items
        wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item);
        if (!layers_item)
            layers_item = add_layer_root_item(object_item);
        else if (printer_technology() == ptSLA) {
            // If layers root item will be deleted from the list, so
            // it's need to be deleted from selection array, if it was there
            wxDataViewItemArray del_items;
            bool some_layers_was_selected = false;
            m_objects_model->GetAllChildren(layers_item, del_items);
            for (auto& del_item:del_items)
                if (sel.Index(del_item) != wxNOT_FOUND) {
                    some_layers_was_selected = true;
                    sel.Remove(del_item);
                }
            if (sel.Index(layers_item) != wxNOT_FOUND) {
                some_layers_was_selected = true;
                sel.Remove(layers_item);
            }

            // delete all "layers" items
            m_objects_model->Delete(layers_item);

            // Select object_item, if layers_item doesn't exist for item anymore, but was some of layer items was/were selected
            if (some_layers_was_selected)
                sel.Add(object_item);
        }
        else {
            wxDataViewItemArray all_obj_layers;
            m_objects_model->GetChildren(layers_item, all_obj_layers);

            for (auto item : all_obj_layers)
                // update settings for layer
                update_settings_item_and_selection(item, sel);
        }
    }

    // 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<int>& 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 = int(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
    const size_t new_obj_indx = static_cast<size_t>(m_objects->size() - 1);
    add_object_to_list(new_obj_indx);

    for (std::set<int>::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);
    }

    // update printable state for new volumes on canvas3D
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(new_obj_indx);
}

void ObjectList::instances_to_separated_objects(const int obj_idx)
{
    const int inst_cnt = (*m_objects)[obj_idx]->instances.size();

    std::vector<size_t> object_idxs;

    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
        const size_t new_obj_indx = static_cast<size_t>(m_objects->size() - 1);
        add_object_to_list(new_obj_indx);
        object_idxs.push_back(new_obj_indx);

        // delete current instance from the initial object
        del_subobject_from_object(obj_idx, i, itInstance);
        delete_instance_from_list(obj_idx, i);
    }

    // update printable state for new volumes on canvas3D
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs);
}

void ObjectList::split_instances()
{
    const Selection& selection = scene_selection();
    const int obj_idx = selection.get_object_idx();
    if (obj_idx == -1)
        return;

    Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Instances to Separated Objects")));

    if (selection.is_single_full_object())
    {
        instances_to_separated_objects(obj_idx);
        return;
    }

    const int inst_idx = selection.get_instance_idx();
    const std::set<int> inst_idxs = inst_idx < 0 ?
                                    selection.get_instance_idxs() :
                                    std::set<int>{ 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, colName);

    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, colName);
    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(colName    )->SetWidth(20 * em);
    GetColumn(colPrint   )->SetWidth( 3 * em);
    GetColumn(colExtruder)->SetWidth( 8 * em);
    GetColumn(colEditing )->SetWidth( 3 * em);

    // rescale all icons, used by ObjectList
    msw_rescale_icons();

    // rescale/update existing items with bitmaps
    m_objects_model->Rescale();

    // rescale menus
    for (MenuWithSeparators* menu : { &m_menu_object, 
                                      &m_menu_part, 
                                      &m_menu_sla_object, 
                                      &m_menu_instance, 
                                      &m_menu_layer,
                                      &m_menu_default})
        msw_rescale_menu(menu);

    Layout();
}

void ObjectList::sys_color_changed()
{
    // msw_rescale_icons() updates icons, so use it
    msw_rescale_icons();

    // update existing items with bitmaps
    m_objects_model->Rescale();

    // msw_rescale_menu updates just icons, so use it
    for (MenuWithSeparators* menu : { &m_menu_object, 
                                      &m_menu_part, 
                                      &m_menu_sla_object, 
                                      &m_menu_instance, 
                                      &m_menu_layer,
                                      &m_menu_default})
        msw_rescale_menu(menu);

    Layout();
}

void ObjectList::ItemValueChanged(wxDataViewEvent &event)
{
    if (event.GetColumn() == colName)
        update_name_in_model(event.GetItem());
    else if (event.GetColumn() == colExtruder)
        update_extruder_in_config(event.GetItem());
}

#ifdef __WXMSW__
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
// Here the last active column is forgotten, so when leaving the editing mode, the next mouse click will not enter the editing mode of the newly selected column.
void ObjectList::OnEditingStarted(wxDataViewEvent &event)
{
	m_last_selected_column = -1;
}
#endif //__WXMSW__

void ObjectList::OnEditingDone(wxDataViewEvent &event)
{
    if (event.GetColumn() != colName)
        return;

    const auto renderer = dynamic_cast<BitmapTextRenderer*>(GetColumn(colName)->GetRenderer());

    if (renderer->WasCanceled())
		wxTheApp->CallAfter([this]{
			show_error(this, _(L("The supplied name is not valid;")) + "\n" +
				             _(L("the following characters are not allowed:")) + " <>:/\\|?*\"");
		});

#ifdef __WXMSW__
	// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
	// Here the last active column is forgotten, so when leaving the editing mode, the next mouse click will not enter the editing mode of the newly selected column.
	m_last_selected_column = -1;
#endif //__WXMSW__

    Plater* plater = wxGetApp().plater();
    if (plater)
        plater->set_current_canvas_as_dirty();
}

void ObjectList::show_multi_selection_menu()
{
    wxDataViewItemArray sels;
    GetSelections(sels);

    for (const wxDataViewItem& item : sels)
        if (!(m_objects_model->GetItemType(item) & (itVolume | itObject | itInstance)))
            // show this menu only for Objects(Instances mixed with Objects)/Volumes selection
            return;

    wxMenu* menu = new wxMenu();

    if (extruders_count() > 1)
        append_menu_item_change_extruder(menu);

    append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")),
        [this](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() {
        return wxGetApp().plater()->can_reload_from_disk();
    }, wxGetApp().plater());

    append_menu_item_convert_unit(menu);
    if (can_merge_to_multipart_object())
        append_menu_item_merge_to_multipart_object(menu);

    wxGetApp().plater()->PopupMenu(menu);
}

void ObjectList::extruder_selection()
{
    wxArrayString choices;
    choices.Add(_(L("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 == _(L("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);

    if (!sels.empty())
        take_snapshot(_(L("Change Extruders")));

    for (const wxDataViewItem& item : sels)
    {
        ModelConfig& config = get_item_config(item);
        
        if (config.has("extruder")) {
            if (extruder == 0)
                config.erase("extruder");
            else
                config.set("extruder", extruder);
        }
        else if (extruder > 0)
            config.set_key_value("extruder", new ConfigOptionInt(extruder));

        const wxString extruder_str = extruder == 0 ? wxString (_(L("default"))) : 
                                      wxString::Format("%d", config.extruder());

        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->SetExtruder(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item);

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

void ObjectList::update_after_undo_redo()
{
    m_prevent_canvas_selection_update = true;

    Plater::SuppressSnapshots suppress(wxGetApp().plater());

    // Unselect all objects before deleting them, so that no change of selection is emitted during deletion.

    /* To avoid execution of selection_changed() 
     * from wxEVT_DATAVIEW_SELECTION_CHANGED emitted from DeleteAll(), 
     * wrap this two functions into m_prevent_list_events *
     * */
    m_prevent_list_events = true;
    this->UnselectAll();
    m_objects_model->DeleteAll();
    m_prevent_list_events = false;

    size_t obj_idx = 0;
    std::vector<size_t> obj_idxs;
    obj_idxs.reserve(m_objects->size());
    while (obj_idx < m_objects->size()) {
        add_object_to_list(obj_idx, false);
        obj_idxs.push_back(obj_idx);
        ++obj_idx;
    }

    update_selections();

    m_prevent_canvas_selection_update = false;

    // update printable states on canvas
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
    // update scene
    wxGetApp().plater()->update();
}

void ObjectList::update_printable_state(int obj_idx, int instance_idx)
{
    ModelObject* object = (*m_objects)[obj_idx];

    const PrintIndicator printable = object->instances[instance_idx]->printable ? piPrintable : piUnprintable;
    if (object->instances.size() == 1)
        instance_idx = -1;

    m_objects_model->SetPrintableState(printable, obj_idx, instance_idx);
}

void ObjectList::toggle_printable_state(wxDataViewItem item)
{
    const ItemType type = m_objects_model->GetItemType(item);
    if (!(type&(itObject|itInstance/*|itVolume*/)))
        return;

    if (type & itObject)
    {
        const int obj_idx = m_objects_model->GetObjectIdByItem(item);
        ModelObject* object = (*m_objects)[obj_idx];

        // get object's printable and change it
        const bool printable = !m_objects_model->IsPrintable(item);

        const wxString snapshot_text = from_u8((boost::format("%1% %2%")
                                                  % (printable ? _(L("Set Printable")) : _(L("Set Unprintable")))
                                                  % object->name).str());
        take_snapshot(snapshot_text);

        // set printable value for all instances in object
        for (auto inst : object->instances)
            inst->printable = printable;

        // update printable state on canvas
        wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx);

        // update printable state in ObjectList
        m_objects_model->SetObjectPrintableState(printable ? piPrintable : piUnprintable , item);
    }
    else
        wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); 

    // update scene
    wxGetApp().plater()->update();
}

ModelObject* ObjectList::object(const int obj_idx) const
{
    if (obj_idx < 0)
        return nullptr;

    return (*m_objects)[obj_idx];
}

} //namespace GUI
} //namespace Slic3r 
