diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 64d50591a..a2618b44e 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -28,6 +28,7 @@ src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp src/slic3r/GUI/GUI.cpp src/slic3r/GUI/GUI_App.cpp src/slic3r/GUI/GUI_Init.cpp +src/slic3r/GUI/GUI_Factories.cpp src/slic3r/GUI/GUI_ObjectLayers.cpp src/slic3r/GUI/GUI_ObjectList.cpp src/slic3r/GUI/GUI_ObjectManipulation.cpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5f806955e..d48443181 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1302,52 +1302,54 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b void ModelObject::split(ModelObjectPtrs* new_objects) { - if (this->volumes.size() > 1) { - // We can't split meshes if there's more than one volume, because - // we can't group the resulting meshes by object afterwards - new_objects->emplace_back(this); - return; - } - - ModelVolume* volume = this->volumes.front(); - TriangleMeshPtrs meshptrs = volume->mesh().split(); - size_t counter = 1; - for (TriangleMesh *mesh : meshptrs) { + for (ModelVolume* volume : this->volumes) { + if (volume->type() != ModelVolumeType::MODEL_PART) + continue; - // FIXME: crashes if not satisfied - if (mesh->facets_count() < 3) continue; + TriangleMeshPtrs meshptrs = volume->mesh().split(); + size_t counter = 1; + for (TriangleMesh* mesh : meshptrs) { - mesh->repair(); - - // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? - ModelObject* new_object = m_model->add_object(); - new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : ""); + // FIXME: crashes if not satisfied + if (mesh->facets_count() < 3) continue; - // Don't copy the config's ID. - new_object->config.assign_config(this->config); - assert(new_object->config.id().valid()); - assert(new_object->config.id() != this->config.id()); - new_object->instances.reserve(this->instances.size()); - for (const ModelInstance *model_instance : this->instances) - new_object->add_instance(*model_instance); - ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); + mesh->repair(); - for (ModelInstance* model_instance : new_object->instances) - { - Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); - model_instance->set_offset(model_instance->get_offset() + shift); + // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? + ModelObject* new_object = m_model->add_object(); + if (meshptrs.size() == 1) { + new_object->name = volume->name; + // Don't copy the config's ID. + new_object->config.assign_config(this->config.size() > 0 ? this->config : volume->config); + } + else { + new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : ""); + // Don't copy the config's ID. + new_object->config.assign_config(this->config); + } + assert(new_object->config.id().valid()); + assert(new_object->config.id() != this->config.id()); + new_object->instances.reserve(this->instances.size()); + for (const ModelInstance* model_instance : this->instances) + new_object->add_instance(*model_instance); + ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); + + for (ModelInstance* model_instance : new_object->instances) + { + Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); + model_instance->set_offset(model_instance->get_offset() + shift); + } + + new_vol->set_offset(Vec3d::Zero()); + // reset the source to disable reload from disk + new_vol->source = ModelVolume::Source(); + new_objects->emplace_back(new_object); + delete mesh; } - - new_vol->set_offset(Vec3d::Zero()); - // reset the source to disable reload from disk - new_vol->source = ModelVolume::Source(); - new_objects->emplace_back(new_object); - delete mesh; } - - return; } + void ModelObject::merge() { if (this->volumes.size() == 1) { @@ -1738,6 +1740,7 @@ size_t ModelVolume::split(unsigned int max_extruders) this->object->volumes[ivolume]->translate(offset); this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); + this->object->volumes[ivolume]->m_is_splittable = 0; delete mesh; ++ idx; } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 40d74159d..6436de2ca 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -93,6 +93,8 @@ set(SLIC3R_GUI_SOURCES GUI/SavePresetDialog.cpp GUI/PhysicalPrinterDialog.hpp GUI/PhysicalPrinterDialog.cpp + GUI/GUI_Factories.cpp + GUI/GUI_Factories.hpp GUI/GUI_ObjectList.cpp GUI/GUI_ObjectList.hpp GUI/GUI_ObjectManipulation.cpp diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp new file mode 100644 index 000000000..d8518a6c8 --- /dev/null +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -0,0 +1,1027 @@ +#include "libslic3r/libslic3r.h" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Model.hpp" + +#include "GUI_Factories.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" +#include "Plater.hpp" +#include "ObjectDataViewModel.hpp" + +#include "OptionsGroup.hpp" +#include "GLCanvas3D.hpp" +#include "Selection.hpp" +#include "format.hpp" + +#include +#include "slic3r/Utils/FixModelByWin10.hpp" + +namespace Slic3r +{ +namespace GUI +{ + +static PrinterTechnology printer_technology() +{ + return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); +} + +static int extruders_count() +{ + return wxGetApp().extruders_edited_cnt(); +} + +static bool is_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"); +} + + +//------------------------------------- +// SettingsFactory +//------------------------------------- + +// pt_FFF +static SettingsFactory::Bundle 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_interface_pattern", "support_material_buildplate_only", + "support_material_spacing" } }, + { L("Wipe options") , { "wipe_into_infill", "wipe_into_objects" } } +}; + +// pt_SLA +static SettingsFactory::Bundle FREQ_SETTINGS_BUNDLE_SLA = +{ + { L("Pad and Support") , { "supports_enable", "pad_enable" } } +}; + +std::vector SettingsFactory::get_options(const bool is_part) +{ + if (printer_technology() == ptSLA) { + SLAPrintObjectConfig full_sla_config; + auto options = full_sla_config.keys(); + options.erase(find(options.begin(), options.end(), "layer_height")); + return options; + } + + PrintRegionConfig reg_config; + auto options = reg_config.keys(); + if (!is_part) { + PrintObjectConfig obj_config; + std::vector obj_options = obj_config.keys(); + options.insert(options.end(), obj_options.begin(), obj_options.end()); + } + return options; +} + +SettingsFactory::Bundle SettingsFactory::get_bundle(const DynamicPrintConfig* config, bool is_object_settings) +{ + auto opt_keys = config->keys(); + if (opt_keys.empty()) + return Bundle(); + + // update options list according to print technology + auto full_current_opts = get_options(!is_object_settings); + 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); + + if (opt_keys.empty()) + return Bundle(); + + const int extruders_cnt = wxGetApp().extruders_edited_cnt(); + + Bundle bundle; + for (auto& opt_key : opt_keys) + { + auto category = config->def()->get(opt_key)->category; + if (is_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; +} + +// Fill CategoryItem +std::map SettingsFactory::CATEGORY_ICON = +{ +// settings category name related bitmap name + // ptFFF + { L("Layers and Perimeters"), "layers" }, + { L("Infill") , "infill" }, + { L("Ironing") , "ironing" }, + { L("Fuzzy Skin") , "fuzzy_skin" }, + { L("Support material") , "support" }, + { L("Speed") , "time" }, + { L("Extruders") , "funnel" }, + { L("Extrusion Width") , "funnel" }, + { L("Wipe options") , "funnel" }, + { L("Skirt and brim") , "skirt+brim" }, +// { L("Speed > Acceleration") , "time" }, + { L("Advanced") , "wrench" }, + // ptSLA , + { L("Supports") , "support" }, + { L("Pad") , "pad" }, + { L("Hollowing") , "hollowing" } +}; + +wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name) +{ + if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) + return wxNullBitmap; + return create_scaled_bitmap(CATEGORY_ICON.at(category_name)); +} + + +//------------------------------------- +// MenuFactory +//------------------------------------- + +// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important +std::vector> MenuFactory::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 Plater* plater() +{ + return wxGetApp().plater(); +} + +static ObjectList* obj_list() +{ + return wxGetApp().obj_list(); +} + +static ObjectDataViewModel* list_model() +{ + return wxGetApp().obj_list()->GetModel(); +} + +static const Selection& get_selection() +{ + return plater()->canvas3D()->get_selection(); +} + +// category -> vector ( option ; label ) +typedef std::map< std::string, std::vector< std::pair > > FullSettingsHierarchy; +static void get_full_settings_hierarchy(FullSettingsHierarchy& settings_menu, const bool is_part) +{ + auto options = SettingsFactory::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 (is_improper_category(category, extruders_cnt, !is_part)) + continue; + + const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label; + std::pair option_label(option, label); + std::vector< std::pair > new_category; + auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); + cat_opt_label.push_back(option_label); + if (cat_opt_label.size() == 1) + settings_menu[category] = cat_opt_label; + } +} + +static wxMenu* create_settings_popupmenu(wxMenu* parent_menu, const bool is_object_settings, wxDataViewItem item/*, ModelConfig& config*/) +{ + wxMenu* menu = new wxMenu; + + FullSettingsHierarchy categories; + get_full_settings_hierarchy(categories, !is_object_settings); + + auto get_selected_options_for_category = [categories, item](const wxString& category_name) { + wxArrayString names; + wxArrayInt selections; + + std::vector< std::pair > category_options; + for (auto& cat : categories) { + if (_(cat.first) == category_name) { + ModelConfig& config = obj_list()->get_item_config(item); + auto opt_keys = config.keys(); + + int sel = 0; + for (const std::pair& pair : cat.second) { + names.Add(_(pair.second)); + if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) + selections.Add(sel); + sel++; + category_options.push_back(std::make_pair(pair.first, false)); + } + break; + } + } + + if (!category_options.empty() && + wxGetSelectedChoices(selections, _L("Select showing settings"), category_name, names) != -1) { + for (auto sel : selections) + category_options[sel].second = true; + } + return category_options; + +#if 0 + if (selections.size() > 0) + { + // Add selected items to the "Quick menu" + SettingsFactory::Bundle& freq_settings = printer_technology() == ptSLA ? + m_freq_settings_sla : m_freq_settings_fff; + bool changed_existing = false; + + std::vector tmp_freq_cat = {}; + + for (auto& cat : freq_settings) + { + if (_(cat.first) == category_name) + { + std::vector& freq_settings_category = cat.second; + freq_settings_category.clear(); + freq_settings_category.reserve(selection_cnt); + for (auto sel : selections) + freq_settings_category.push_back((*settings_list)[sel].first); + + changed_existing = true; + break; + } + } + + if (!changed_existing) + { + // Create new "Quick menu" item + for (auto& cat : settings_menu) + { + if (_(cat.first) == category_name) + { + freq_settings[cat.first] = std::vector{}; + + std::vector& freq_settings_category = freq_settings.find(cat.first)->second; + freq_settings_category.reserve(selection_cnt); + for (auto sel : selections) + freq_settings_category.push_back((*settings_list)[sel].first); + break; + } + } + } + } +#endif + }; + + for (auto cat : categories) { + append_menu_item(menu, wxID_ANY, _(cat.first), "", + [menu, item, get_selected_options_for_category](wxCommandEvent& event) { + std::vector< std::pair > category_options = get_selected_options_for_category(menu->GetLabel(event.GetId())); + obj_list()->add_category_to_settings_from_selection(category_options, item); + }, SettingsFactory::get_category_bitmap(cat.first), parent_menu, + []() { return true; }, plater()); + } + + return menu; +} + +static void create_freq_settings_popupmenu(wxMenu* menu, const bool is_object_settings, wxDataViewItem item) +{ + // Add default settings bundles + const SettingsFactory::Bundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; + + const int extruders_cnt = extruders_count(); + + for (auto& category : bundle) { + if (is_improper_category(category.first, extruders_cnt, is_object_settings)) + continue; + + append_menu_item(menu, wxID_ANY, _(category.first), "", + [menu, item, is_object_settings, bundle](wxCommandEvent& event) { + wxString category_name = menu->GetLabel(event.GetId()); + std::vector options; + for (auto& category : bundle) + if (category_name == _(category.first)) { + options = category.second; + break; + } + if (options.empty()) + return; + // 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 (!is_object_settings && category_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); + } + + obj_list()->add_category_to_settings_from_frequent(options, item); + }, + SettingsFactory::get_category_bitmap(category.first), menu, + []() { return true; }, plater()); + } +#if 0 + // Add "Quick" settings bundles + const SettingsFactory::Bundle& bundle_quick = printer_technology() == ptFFF ? m_freq_settings_fff : m_freq_settings_sla; + + for (auto& category : bundle_quick) { + if (is_improper_category(category.first, extruders_cnt)) + continue; + + append_menu_item(menu, wxID_ANY, from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()), "", + [menu, item, is_object_settings, bundle](wxCommandEvent& event) { + wxString category_name = menu->GetLabel(event.GetId()); + std::vector options; + for (auto& category : bundle) + if (category_name == from_u8((boost::format(_L("Quick Add Settings (%s)")) % _(category.first)).str())) { + options = category.second; + break; + } + if (options.empty()) + return; + // 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 (!is_object_settings) { + const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); + if (layer_height_it != options.end()) + options.erase(layer_height_it); + } + obj_list()->add_category_to_settings_from_frequent(options, item); + }, + SettingsFactory::get_category_bitmap(category.first), menu, + [this]() { return true; }, plater()); + } +#endif +} + +std::vector MenuFactory::get_volume_bitmaps() +{ + std::vector volume_bmps; + volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size()); + for (auto item : ADD_VOLUME_MENU_ITEMS) + volume_bmps.push_back(create_scaled_bitmap(item.second)); + return volume_bmps; +} + +void MenuFactory::append_menu_item_delete(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), + [](wxCommandEvent&) { plater()->remove_selected(); }, "delete", nullptr, + []() { return plater()->can_delete(); }, m_parent); + + menu->AppendSeparator(); + +} + +wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, 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, "", + [type](wxCommandEvent&) { obj_list()->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), "", + [type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu); + } + + return sub_menu; +} + +void MenuFactory::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(); + + if (mode == comAdvanced) { + append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "", + [](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); }, + ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } + if (mode == comSimple) { + append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "", + [](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); }, + ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].first), "", + [](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); }, + ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second, nullptr, + []() { return obj_list()->is_instance_or_object_selected(); }, m_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, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + } +} + +wxMenuItem* MenuFactory::append_menu_item_layers_editing(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _L("Height range Modifier"), "", + [](wxCommandEvent&) { obj_list()->layers_editing(); }, "edit_layers_all", menu, + []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); +} + +wxMenuItem* MenuFactory::append_menu_item_settings(wxMenu* menu_) +{ + MenuWithSeparators* menu = dynamic_cast(menu_); + + const wxString menu_name = _L("Add settings"); + // Delete old items from settings popupmenu + auto settings_id = menu->FindItem(menu_name); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + + for (auto& it : FREQ_SETTINGS_BUNDLE_FFF) + { + settings_id = menu->FindItem(_(it.first)); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } + for (auto& it : FREQ_SETTINGS_BUNDLE_SLA) + { + settings_id = menu->FindItem(_(it.first)); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } +#if 0 + for (auto& it : m_freq_settings_fff) + { + settings_id = menu->FindItem(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 = get_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 = obj_list()->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(); + + // detect itemm for adding of the setting + ObjectList* object_list = obj_list(); + ObjectDataViewModel* obj_model = list_model(); + + const wxDataViewItem sel_item = // when all instances in object are selected + object_list->GetSelectedItemsCount() > 1 && selection.is_single_full_object() ? + obj_model->GetItemById(selection.get_object_idx()) : + object_list->GetSelection(); + if (!sel_item) + return nullptr; + + // 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 = obj_model->GetItemType(sel_item) & itSettings ? obj_model->GetParent(sel_item) : sel_item; + const ItemType item_type = obj_model->GetItemType(item); + const bool is_object_settings = !(item_type& itVolume || item_type & itLayer); + + // Add frequently settings + create_freq_settings_popupmenu(menu, is_object_settings, item); + + if (mode == comAdvanced) + return nullptr; + + menu->SetSecondSeparator(); + + // Add full settings list + auto menu_item = new wxMenuItem(menu, wxID_ANY, menu_name); + menu_item->SetBitmap(create_scaled_bitmap("cog")); + menu_item->SetSubMenu(create_settings_popupmenu(menu, is_object_settings, item)); + + return menu->Append(menu_item); +} + +wxMenuItem* MenuFactory::append_menu_item_change_type(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _L("Change type"), "", + [](wxCommandEvent&) { obj_list()->change_part_type(); }, "", menu, + []() { + wxDataViewItem item = obj_list()->GetSelection(); + return item.IsOk() || obj_list()->GetModel()->GetItemType(item) == itVolume; + }, m_parent); +} + +wxMenuItem* MenuFactory::append_menu_item_instance_to_object(wxMenu* menu) +{ + wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Set as a Separated Object"), "", + [](wxCommandEvent&) { obj_list()->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 + */ + m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) + { + const Selection& selection = 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(plater()->can_set_instance_to_object()); + }, menu_item->GetId()); + + return menu_item; +} + +wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu) +{ + return append_menu_check_item(menu, wxID_ANY, _L("Printable"), "", [](wxCommandEvent&) { + const Selection& selection = plater()->canvas3D()->get_selection(); + wxDataViewItem item; + if (obj_list()->GetSelectedItemsCount() > 1 && selection.is_single_full_object()) + item = obj_list()->GetModel()->GetItemById(selection.get_object_idx()); + else + item = obj_list()->GetSelection(); + + if (item) + obj_list()->toggle_printable_state(item); + }, menu); +} + +void MenuFactory::append_menu_items_osx(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Rename"), "", + [](wxCommandEvent&) { obj_list()->rename_item(); }, "", menu); + + menu->AppendSeparator(); +} + +wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu) +{ + if (!is_windows10()) + return nullptr; + wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix through the Netfabb"), "", + [](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu, + []() {return plater()->can_fix_through_netfabb(); }, plater()); + menu->AppendSeparator(); + + return menu_item; +} + +void MenuFactory::append_menu_item_export_stl(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, "", + [](wxCommandEvent&) { plater()->export_stl(false, true); }, "", nullptr, + []() { + const Selection& selection = plater()->canvas3D()->get_selection(); + return selection.is_single_full_instance() || selection.is_single_full_object(); + }, m_parent); + menu->AppendSeparator(); +} + +void MenuFactory::append_menu_item_reload_from_disk(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected volumes from disk"), + [](wxCommandEvent&) { plater()->reload_from_disk(); }, "", menu, + []() { return plater()->can_reload_from_disk(); }, m_parent); +} + +void MenuFactory::append_menu_item_change_extruder(wxMenu* menu) +{ + const std::vector 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; + obj_list()->GetSelections(sels); + if (sels.IsEmpty()) + return; + + std::vector 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 = obj_list()->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, "", + [i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, *icons[icon_idx], menu, + [is_active_extruder]() { return !is_active_extruder; }, m_parent); + + } + + menu->AppendSubMenu(extruder_selection_menu, name); +} + +void MenuFactory::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&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu); +} + +void MenuFactory::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/) +{ + std::vector obj_idxs, vol_idxs; + obj_list()->get_selection_indexes(obj_idxs, vol_idxs); + if (obj_idxs.empty() && vol_idxs.empty()) + return; + + auto volume_respects_conversion = [](ModelVolume* volume, ConversionType conver_type) + { + return (conver_type == ConversionType::CONV_FROM_INCH && volume->source.is_converted_from_inches) || + (conver_type == ConversionType::CONV_TO_INCH && !volume->source.is_converted_from_inches) || + (conver_type == ConversionType::CONV_FROM_METER && volume->source.is_converted_from_meters) || + (conver_type == ConversionType::CONV_TO_METER && !volume->source.is_converted_from_meters); + }; + + auto can_append = [obj_idxs, vol_idxs, volume_respects_conversion](ConversionType conver_type) + { + ModelObjectPtrs objects; + for (int obj_idx : obj_idxs) { + ModelObject* object = obj_list()->object(obj_idx); + if (vol_idxs.empty()) { + for (ModelVolume* volume : object->volumes) + if (volume_respects_conversion(volume, conver_type)) + return false; + } + else { + for (int vol_idx : vol_idxs) + if (volume_respects_conversion(object->volumes[vol_idx], conver_type)) + return false; + } + } + return true; + }; + + std::vector> items = { + {ConversionType::CONV_FROM_INCH , _L("Convert from imperial units") }, + {ConversionType::CONV_TO_INCH , _L("Revert conversion from imperial units") }, + {ConversionType::CONV_FROM_METER, _L("Convert from meters") }, + {ConversionType::CONV_TO_METER , _L("Revert conversion from meters") } }; + + for (auto item : items) { + int menu_id = menu->FindItem(item.second); + if (can_append(item.first)) { + // Add menu item if it doesn't exist + if (menu_id == wxNOT_FOUND) + append_menu_item(menu, wxID_ANY, item.second, item.second, + [item](wxCommandEvent&) { plater()->convert_unit(item.first); }, "", menu, + []() { return true; }, m_parent, insert_pos); + } + else if (menu_id != wxNOT_FOUND) { + // Delete menu item + menu->Destroy(menu_id); + } + } +} + +void MenuFactory::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"), + [](wxCommandEvent&) { obj_list()->merge(true); }, "", menu, + []() { return obj_list()->can_merge_to_multipart_object(); }, m_parent); +} +/* +void MenuFactory::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"), + [](wxCommandEvent&) { obj_list()->merge(false); }, "", menu, + []() { return obj_list()->can_merge_to_single_object(); }, m_parent); +} +*/ +void MenuFactory::append_menu_items_mirror(wxMenu* menu) +{ + wxMenu* mirror_menu = new wxMenu(); + if (!mirror_menu) + return; + + append_menu_item(mirror_menu, wxID_ANY, _L("Along X axis"), _L("Mirror the selected object along the X axis"), + [](wxCommandEvent&) { plater()->mirror(X); }, "mark_X", menu); + append_menu_item(mirror_menu, wxID_ANY, _L("Along Y axis"), _L("Mirror the selected object along the Y axis"), + [](wxCommandEvent&) { plater()->mirror(Y); }, "mark_Y", menu); + append_menu_item(mirror_menu, wxID_ANY, _L("Along Z axis"), _L("Mirror the selected object along the Z axis"), + [](wxCommandEvent&) { plater()->mirror(Z); }, "mark_Z", menu); + + append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror the selected object"), "", + []() { return plater()->can_mirror(); }, m_parent); +} + +MenuFactory::MenuFactory() +{ + for (int i = 0; i < mtCount; i++) { + items_increase[i] = nullptr; + items_decrease[i] = nullptr; + items_set_number_of_copies[i] = nullptr; + } +} + +void MenuFactory::create_default_menu() +{ + wxMenu* sub_menu = append_submenu_add_generic(&m_default_menu, ModelVolumeType::INVALID); + append_submenu(&m_default_menu, sub_menu, wxID_ANY, _L("Add Shape"), "", "add_part", + []() {return true; }, m_parent); +} + +void MenuFactory::create_common_object_menu(wxMenu* menu) +{ +#ifdef __WXOSX__ + append_menu_items_osx(menu); +#endif // __WXOSX__ + append_menu_items_instance_manipulation(menu); + // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake. + append_menu_item_delete(menu); + append_menu_item_instance_to_object(menu); + menu->AppendSeparator(); + + wxMenuItem* menu_item_printable = append_menu_item_printable(menu); + menu->AppendSeparator(); + + append_menu_item_reload_from_disk(menu); + append_menu_item_export_stl(menu); + // "Scale to print volume" makes a sense just for whole object + append_menu_item_scale_selection_to_fit_print_volume(menu); + + append_menu_item_fix_through_netfabb(menu); + append_menu_items_mirror(menu); + + m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) { + const Selection& selection = get_selection(); + int instance_idx = selection.get_instance_idx(); + evt.Enable(selection.is_single_full_instance() || selection.is_single_full_object()); + if (instance_idx != -1) { + evt.Check(obj_list()->object(selection.get_object_idx())->instances[instance_idx]->printable); + plater()->set_current_canvas_as_dirty();//view3D->set_as_dirty(); + } + }, menu_item_printable->GetId()); +} + +void MenuFactory::create_object_menu() +{ + create_common_object_menu(&m_object_menu); + wxMenu* split_menu = new wxMenu(); + if (!split_menu) + return; + + append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"), + [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", &m_object_menu, + []() { return plater()->can_split(true); }, m_parent); + append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual parts"), + [](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", &m_object_menu, + []() { return plater()->can_split(false); }, m_parent); + + append_submenu(&m_object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "", + []() { return plater()->can_split(true) && wxGetApp().get_mode() > comSimple; }, m_parent); + m_object_menu.AppendSeparator(); + + // Layers Editing for object + append_menu_item_layers_editing(&m_object_menu); + m_object_menu.AppendSeparator(); + + // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume() +} + +void MenuFactory::create_sla_object_menu() +{ + create_common_object_menu(&m_sla_object_menu); + append_menu_item(&m_sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"), + [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", nullptr, + []() { return plater()->can_split(true); }, m_parent); + + m_sla_object_menu.AppendSeparator(); + + // Add the automatic rotation sub-menu + append_menu_item(&m_sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."), + [](wxCommandEvent&) { plater()->optimize_rotation(); }); +} + +void MenuFactory::create_part_menu() +{ + wxMenu* menu = &m_part_menu; +#ifdef __WXOSX__ + append_menu_items_osx(menu); +#endif // __WXOSX__ + append_menu_item_delete(menu); + append_menu_item_reload_from_disk(menu); + append_menu_item_export_stl(menu); + append_menu_item_fix_through_netfabb(menu); + append_menu_items_mirror(menu); + + append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"), + [](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", nullptr, + []() { return plater()->can_split(false); }, m_parent); + + menu->AppendSeparator(); + append_menu_item_change_type(menu); + +} + +void MenuFactory::init(wxWindow* parent) +{ + m_parent = parent; + + create_default_menu(); + create_object_menu(); + create_sla_object_menu(); + create_part_menu(); + + // create "Instance to Object" menu item + append_menu_item_instance_to_object(&m_instance_menu); +} + +wxMenu* MenuFactory::default_menu() +{ + return &m_default_menu; +} + +wxMenu* MenuFactory::object_menu() +{ + append_menu_items_convert_unit(&m_object_menu, 11); + append_menu_item_settings(&m_object_menu); + append_menu_item_change_extruder(&m_object_menu); + update_menu_items_instance_manipulation(mtObjectFFF); + + return &m_object_menu; +} + +wxMenu* MenuFactory::sla_object_menu() +{ + append_menu_items_convert_unit(&m_sla_object_menu, 11); + append_menu_item_settings(&m_sla_object_menu); + update_menu_items_instance_manipulation(mtObjectSLA); + + return &m_sla_object_menu; +} + +wxMenu* MenuFactory::part_menu() +{ + append_menu_items_convert_unit(&m_part_menu, 2); + append_menu_item_settings(&m_part_menu); + append_menu_item_change_extruder(&m_part_menu); + + return &m_part_menu; +} + +wxMenu* MenuFactory::instance_menu() +{ + return &m_instance_menu; +} + +wxMenu* MenuFactory::layer_menu() +{ + MenuWithSeparators* menu = new MenuWithSeparators(); + append_menu_item_settings(menu); + + return menu; +} + +wxMenu* MenuFactory::multi_selection_menu() +{ + wxDataViewItemArray sels; + obj_list()->GetSelections(sels); + + for (const wxDataViewItem& item : sels) + if (!(list_model()->GetItemType(item) & (itVolume | itObject | itInstance))) + // show this menu only for Objects(Instances mixed with Objects)/Volumes selection + return nullptr; + + wxMenu* menu = new MenuWithSeparators(); + + append_menu_item_reload_from_disk(menu); + append_menu_items_convert_unit(menu); + if (obj_list()->can_merge_to_multipart_object()) + append_menu_item_merge_to_multipart_object(menu); + if (extruders_count() > 1) + append_menu_item_change_extruder(menu); + + return menu; +} + +void MenuFactory::append_menu_items_instance_manipulation(wxMenu* menu) +{ + MenuType type = menu == &m_object_menu ? mtObjectFFF : mtObjectSLA; + + items_increase[type] = append_menu_item(menu, wxID_ANY, _L("Add instance") + "\t+", _L("Add one more instance of the selected object"), + [](wxCommandEvent&) { plater()->increase_instances(); }, "add_copies", nullptr, + []() { return plater()->can_increase_instances(); }, m_parent); + items_decrease[type] = append_menu_item(menu, wxID_ANY, _L("Remove instance") + "\t-", _L("Remove one instance of the selected object"), + [](wxCommandEvent&) { plater()->decrease_instances(); }, "remove_copies", nullptr, + []() { return plater()->can_decrease_instances(); }, m_parent); + items_set_number_of_copies[type] = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"), + [](wxCommandEvent&) { plater()->set_number_of_copies(); }, "number_of_copies", nullptr, + []() { return plater()->can_increase_instances(); }, m_parent); + + append_menu_item(menu, wxID_ANY, _L("Fill bed with instances") + dots, _L("Fill the remaining area of bed with instances of the selected object"), + [](wxCommandEvent&) { plater()->fill_bed_with_instances(); }, "", nullptr, + []() { return plater()->can_increase_instances(); }, m_parent); + +} + +void MenuFactory::update_menu_items_instance_manipulation(MenuType type) +{ + wxMenu* menu = type == mtObjectFFF ? &m_object_menu : type == mtObjectSLA ? &m_sla_object_menu : nullptr; + if (menu) + return; + // Remove/Prepend "increase/decrease instances" menu items according to the view mode. + // Suppress to show those items for a Simple mode + if (wxGetApp().get_mode() == comSimple) { + if (menu->FindItem(_L("Add instance")) != wxNOT_FOUND) + { + // Detach an items from the menu, but don't delete them + // so that they can be added back later + // (after switching to the Advanced/Expert mode) + menu->Remove(items_increase[type]); + menu->Remove(items_decrease[type]); + menu->Remove(items_set_number_of_copies[type]); + } + } + else { + if (menu->FindItem(_L("Add instance")) == wxNOT_FOUND) + { + // Prepend items to the menu, if those aren't not there + menu->Prepend(items_set_number_of_copies[type]); + menu->Prepend(items_decrease[type]); + menu->Prepend(items_increase[type]); + } + } +} + +void MenuFactory::update_object_menu() +{ + append_menu_items_add_volume(&m_object_menu); +} + +void MenuFactory::msw_rescale() +{ + for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu }) + msw_rescale_menu(dynamic_cast(menu)); +} + +} //namespace GUI +} //namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp new file mode 100644 index 000000000..3cc5759a1 --- /dev/null +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -0,0 +1,104 @@ +#ifndef slic3r_GUI_Factories_hpp_ +#define slic3r_GUI_Factories_hpp_ + +#include +#include +#include + +#include + +#include "libslic3r/PrintConfig.hpp" +#include "wxExtensions.hpp" + +class wxMenu; +class wxMenuItem; + +namespace Slic3r { + +enum class ModelVolumeType : int; + +namespace GUI { + +struct SettingsFactory +{ +// category -> vector ( option ) + typedef std::map> Bundle; + static std::map CATEGORY_ICON; + + static wxBitmap get_category_bitmap(const std::string& category_name); + static Bundle get_bundle(const DynamicPrintConfig* config, bool is_object_settings); + static std::vector get_options(bool is_part); +}; + +class MenuFactory +{ +public: + static std::vector> ADD_VOLUME_MENU_ITEMS; + static std::vector get_volume_bitmaps(); + + MenuFactory(); + ~MenuFactory() = default; + + void init(wxWindow* parent); + void update_object_menu(); + void msw_rescale(); + + wxMenu* default_menu(); + wxMenu* object_menu(); + wxMenu* sla_object_menu(); + wxMenu* part_menu(); + wxMenu* instance_menu(); + wxMenu* layer_menu(); + wxMenu* multi_selection_menu(); + +private: + enum MenuType { + mtObjectFFF = 0, + mtObjectSLA, + mtCount + }; + + wxWindow* m_parent {nullptr}; + + MenuWithSeparators m_object_menu; + MenuWithSeparators m_part_menu; + MenuWithSeparators m_sla_object_menu; + MenuWithSeparators m_default_menu; + MenuWithSeparators m_instance_menu; + + // Removed/Prepended Items according to the view mode + std::array items_increase; + std::array items_decrease; + std::array items_set_number_of_copies; + + void create_default_menu(); + void create_common_object_menu(wxMenu *menu); + void create_object_menu(); + void create_sla_object_menu(); + void create_part_menu(); + + wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type); + void append_menu_items_add_volume(wxMenu* menu); + wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); + wxMenuItem* append_menu_item_settings(wxMenu* menu); + wxMenuItem* append_menu_item_change_type(wxMenu* menu); + wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); + wxMenuItem* append_menu_item_printable(wxMenu* menu); + void append_menu_items_osx(wxMenu* menu); + wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); + void append_menu_item_export_stl(wxMenu* menu); + void append_menu_item_reload_from_disk(wxMenu* menu); + void append_menu_item_change_extruder(wxMenu* menu); + void append_menu_item_delete(wxMenu* menu); + void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); + void append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk" + void append_menu_item_merge_to_multipart_object(wxMenu *menu); +// void append_menu_item_merge_to_single_object(wxMenu *menu); + void append_menu_items_mirror(wxMenu *menu); + void append_menu_items_instance_manipulation(wxMenu *menu); + void update_menu_items_instance_manipulation(MenuType type); +}; + +}} + +#endif //slic3r_GUI_Factories_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 8768f39ff..725a396c3 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -260,16 +260,15 @@ void ObjectLayers::msw_rescale() editor->msw_rescale(); } - const std::vector btns = {2, 3}; // del_btn, add_btn - for (auto btn : btns) - { - wxSizerItem* b_item = item->GetSizer()->GetItem(btn); - if (b_item->IsWindow()) { - auto button = dynamic_cast(b_item->GetWindow()); - if (button != nullptr) - button->msw_rescale(); - } - } + if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons + for (size_t btn : {2, 3}) { // del_btn, add_btn + wxSizerItem* b_item = item->GetSizer()->GetItem(btn); + if (b_item->IsWindow()) { + auto button = dynamic_cast(b_item->GetWindow()); + if (button != nullptr) + button->msw_rescale(); + } + } } } m_grid_sizer->Layout(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 57f1841e3..f37276beb 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PresetBundle.hpp" #include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" @@ -31,32 +32,6 @@ 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_interface_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> 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(); @@ -86,35 +61,11 @@ static void take_snapshot(const wxString& snapshot_name) } ObjectList::ObjectList(wxWindow* parent) : - wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), - m_parent(parent) + wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE) { - // 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("Fuzzy Skin")] = create_scaled_bitmap("fuzzy_skin"); - 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 @@ -125,11 +76,6 @@ ObjectList::ObjectList(wxWindow* parent) : #endif #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. @@ -347,16 +293,6 @@ void ObjectList::create_objects_ctrl() } } -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; @@ -381,10 +317,14 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorGetItemType(sels[0]) & itVolume) { + if ( m_objects_model->GetItemType(sels[0]) & itVolume || + (sels.Count()==1 && m_objects_model->GetItemType(m_objects_model->GetParent(sels[0])) & itVolume) ) { for (wxDataViewItem item : sels) { obj_idxs.emplace_back(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item))); + if (sels.Count() == 1 && m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itVolume) + item = m_objects_model->GetParent(item); + assert(m_objects_model->GetItemType(item) & itVolume); vol_idxs.emplace_back(m_objects_model->GetVolumeIdByItem(item)); } @@ -392,8 +332,6 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorGetItemType(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))); } @@ -633,75 +571,6 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const (*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; @@ -821,7 +690,7 @@ void ObjectList::paste_settings_into_list() assert(!config_cache.empty()); auto keys = config_cache.keys(); - auto part_options = get_options(true); + auto part_options = SettingsFactory::get_options(true); for (const std::string& opt_key: keys) { if (item_type & (itVolume | itLayer) && @@ -867,9 +736,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol } select_items(items); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::paste_objects_into_list(const std::vector& object_idxs) @@ -887,9 +754,7 @@ void ObjectList::paste_objects_into_list(const std::vector& object_idxs) wxGetApp().plater()->changed_objects(object_idxs); select_items(items); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } #ifdef __WXOSX__ @@ -1002,39 +867,35 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me void ObjectList::show_context_menu(const bool evt_context_menu) { + wxMenu* menu {nullptr}; + Plater* plater = wxGetApp().plater(); + if (multiple_selection()) { if (selected_instances_of_same_object()) - wxGetApp().plater()->PopupMenu(&m_menu_instance); + menu = plater->instance_menu(); else - show_multi_selection_menu(); - - return; + menu = plater->multi_selection_menu(); } + else { + const auto item = GetSelection(); + if (item) + { + const ItemType type = m_objects_model->GetItemType(item); + if (!(type & (itObject | itVolume | itLayer | itInstance))) + 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_items_convert_unit(menu); - if (!(type & itInstance)) - append_menu_item_settings(menu); + menu = type & itInstance ? plater->instance_menu() : + type & itLayer ? plater->layer_menu() : + m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? plater->part_menu() : + printer_technology() == ptFFF ? plater->object_menu() : plater->sla_object_menu(); + } + else if (evt_context_menu) + menu = plater->default_menu(); } - else if (evt_context_menu) - menu = &m_menu_default; if (menu) - wxGetApp().plater()->PopupMenu(menu); + plater->PopupMenu(menu); } void ObjectList::extruder_editing() @@ -1274,15 +1135,6 @@ void ObjectList::OnDrop(wxDataViewEvent &event) 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" -// * 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) @@ -1320,207 +1172,34 @@ void ObjectList::OnDrop(wxDataViewEvent &event) wxGetApp().plater()->set_current_canvas_as_dirty(); } - -// Context Menu - -std::vector ObjectList::get_options(const bool is_part) +void ObjectList::add_category_to_settings_from_selection(const std::vector< std::pair >& category_options, wxDataViewItem item) { - if (printer_technology() == ptSLA) { - SLAPrintObjectConfig full_sla_config; - auto options = full_sla_config.keys(); - options.erase(find(options.begin(), options.end(), "layer_height")); - return options; - } - - PrintRegionConfig reg_config; - auto options = reg_config.keys(); - if (!is_part) { - PrintObjectConfig obj_config; - std::vector obj_options = obj_config.keys(); - options.insert(options.end(), obj_options.begin(), obj_options.end()); - } - return options; -} - -const std::vector& ObjectList::get_options_for_bundle(const wxString& bundle_name) -{ - const 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 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 option_label(option, label); - std::vector< std::pair > new_category; - auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); - cat_opt_label.push_back(option_label); - if (cat_opt_label.size() == 1) - settings_menu[category] = cat_opt_label; - } -} - -void ObjectList::get_settings_choice(const wxString& category_name) -{ - wxArrayString names; - wxArrayInt selections; - - /* 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; + if (category_options.empty()) + return; 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 > *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 tmp_freq_cat = {}; - - for (auto& cat : freq_settings) - { - if (_(cat.first) == category_name) - { - std::vector& freq_settings_category = cat.second; - freq_settings_category.clear(); - freq_settings_category.reserve(selection_cnt); - for (auto sel : selections) - freq_settings_category.push_back((*settings_list)[sel].first); - - changed_existing = true; - break; - } - } - - if (!changed_existing) - { - // Create new "Quick menu" item - for (auto& cat : settings_menu) - { - if (_(cat.first) == category_name) - { - freq_settings[cat.first] = std::vector {}; - - std::vector& freq_settings_category = freq_settings.find(cat.first)->second; - freq_settings_category.reserve(selection_cnt); - for (auto sel : selections) - freq_settings_category.push_back((*settings_list)[sel].first); - break; - } - } - } - } -#endif - - 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")); + 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 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()) + for (auto& opt : category_options) { + auto& opt_key = opt.first; + if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && !opt.second) 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()) { + if (find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && opt.second) { const ConfigOption* option = from_config.option(opt_key); if (!option) { // if current option doesn't exist in prints.get_edited_preset(), @@ -1531,48 +1210,25 @@ void ObjectList::get_settings_choice(const wxString& category_name) } } - // 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) +void ObjectList::add_category_to_settings_from_frequent(const std::vector& options, wxDataViewItem item) { - std::vector 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")); + 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; @@ -1607,520 +1263,12 @@ void ObjectList::show_settings(const wxDataViewItem settings_item) 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(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")), - [](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 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 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_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/) -{ - std::vector obj_idxs, vol_idxs; - get_selection_indexes(obj_idxs, vol_idxs); - if (obj_idxs.empty() && vol_idxs.empty()) - return; - - auto volume_respects_conversion = [](ModelVolume* volume, ConversionType conver_type) - { - return (conver_type == ConversionType::CONV_FROM_INCH && volume->source.is_converted_from_inches) || - (conver_type == ConversionType::CONV_TO_INCH && !volume->source.is_converted_from_inches) || - (conver_type == ConversionType::CONV_FROM_METER && volume->source.is_converted_from_meters) || - (conver_type == ConversionType::CONV_TO_METER && !volume->source.is_converted_from_meters); - }; - - auto can_append = [this, obj_idxs, vol_idxs, volume_respects_conversion](ConversionType conver_type) - { - 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_respects_conversion(volume, conver_type)) - return false; - } - else { - for (int vol_idx : vol_idxs) - if (volume_respects_conversion(object->volumes[vol_idx], conver_type)) - return false; - } - } - return true; - }; - - std::vector> items = { - {ConversionType::CONV_FROM_INCH , _L("Convert from imperial units") }, - {ConversionType::CONV_TO_INCH , _L("Revert conversion from imperial units") }, - {ConversionType::CONV_FROM_METER, _L("Convert from meters") }, - {ConversionType::CONV_TO_METER , _L("Revert conversion from meters") } }; - - for (auto item : items) { - int menu_id = menu->FindItem(item.second); - if (can_append(item.first)) { - // Add menu item if it doesn't exist - if (menu_id == wxNOT_FOUND) - append_menu_item(menu, wxID_ANY, item.second, item.second, - [item](wxCommandEvent&) { wxGetApp().plater()->convert_unit(item.first); }, "", menu, - []() {return true; }, nullptr, insert_pos); - } - else if (menu_id != wxNOT_FOUND) { - // Delete menu item - menu->Destroy(menu_id); - } - } -} - -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), "", - [this, menu](wxCommandEvent& event) { get_settings_choice(menu->GetLabel(event.GetId())); }, - CATEGORY_ICON.find(cat.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(cat.first), parent_menu, - []() { return 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), "", - [this, menu](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, - []() { 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(); @@ -2152,9 +1300,7 @@ void ObjectList::load_subobject(ModelVolumeType type) 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, @@ -2297,9 +1443,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode 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) @@ -2503,6 +1647,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con wxString extruder = object->config.has("extruder") ? wxString::Format("%d", object->config.extruder()) : _L("default"); m_objects_model->SetExtruder(extruder, obj_item); } + // add settings to the object, if it has them + add_settings_item(obj_item, &object->config.get()); } } } @@ -2863,11 +2009,27 @@ bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& vo return true; } -bool ObjectList::is_splittable() +bool ObjectList::is_splittable(bool to_objects) { const wxDataViewItem item = GetSelection(); if (!item) return false; + if (to_objects) + { + ItemType type = m_objects_model->GetItemType(item); + if (type == itVolume) + return false; + if (type == itObject || m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itObject) { + auto obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) + return false; + if ((*m_objects)[obj_idx]->volumes.size() > 1) + return true; + return (*m_objects)[obj_idx]->volumes[0]->is_splittable(); + } + return false; + } + ModelVolume* volume; if (!get_volume_by_item(item, volume) || !volume) return false; @@ -3044,37 +2206,6 @@ void ObjectList::part_selection_changed() 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) { @@ -3084,7 +2215,13 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D 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 (!is_object_settings) { + ModelVolumeType volume_type = m_objects_model->GetVolumeType(parent_item); + if (volume_type == ModelVolumeType::SUPPORT_BLOCKER || volume_type == ModelVolumeType::SUPPORT_ENFORCER) + return ret; + } + + SettingsFactory::Bundle cat_options = SettingsFactory::get_bundle(config, is_object_settings); if (cat_options.empty()) return ret; @@ -3687,13 +2824,12 @@ void ObjectList::update_selections() 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 ) + // We doesn't update selection if itSettings | itLayerRoot | itLayer Item for the current object/part is selected 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()) + if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject) return; sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } @@ -4161,9 +3297,15 @@ void ObjectList::fix_multiselection_conflicts() ModelVolume* ObjectList::get_selected_model_volume() { - auto item = GetSelection(); - if (!item || m_objects_model->GetItemType(item) != itVolume) + wxDataViewItem item = GetSelection(); + if (!item) return nullptr; + if (m_objects_model->GetItemType(item) != itVolume) { + if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume) + item = m_objects_model->GetParent(item); + else + return nullptr; + } const auto vol_idx = m_objects_model->GetVolumeIdByItem(item); const auto obj_idx = get_selected_obj_idx(); @@ -4206,8 +3348,10 @@ void ObjectList::change_part_type() take_snapshot(_(L("Change Part Type"))); - const auto item = GetSelection(); volume->set_type(new_type); + wxDataViewItem item = GetSelection(); + if (m_objects_model->GetItemType(item) != itVolume && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume) + item = m_objects_model->GetParent(item); m_objects_model->SetVolumeType(item, new_type); changed_object(get_selected_obj_idx()); @@ -4351,11 +3495,6 @@ void ObjectList::update_object_list_by_printer_technology() m_prevent_canvas_selection_update = false; } -void ObjectList::update_object_menu() -{ - append_menu_items_add_volume(&m_menu_object); -} - void ObjectList::instances_to_separated_object(const int obj_idx, const std::set& inst_idxs) { if ((*m_objects)[obj_idx]->instances.size() == inst_idxs.size()) @@ -4528,41 +3667,17 @@ void ObjectList::msw_rescale() 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(); } @@ -4607,33 +3722,6 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) 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")), - [](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() { - return wxGetApp().plater()->can_reload_from_disk(); - }, wxGetApp().plater()); - - append_menu_items_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; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 0846def53..35a8fdee0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -31,18 +31,11 @@ class TriangleMesh; enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: -typedef std::vector t_config_option_keys; - -typedef std::map> SettingsBundle; - -// category -> vector ( option ; label ) -typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; - -typedef std::vector ModelVolumePtrs; - -typedef double coordf_t; -typedef std::pair t_layer_height_range; -typedef std::map t_layer_config_ranges; +typedef std::vector t_config_option_keys; +typedef std::vector ModelVolumePtrs; +typedef double coordf_t; +typedef std::pair t_layer_height_range; +typedef std::map t_layer_config_ranges; namespace GUI { @@ -106,7 +99,7 @@ public: private: SELECTION_MODE m_selection_mode {smUndef}; - int m_selected_layers_range_idx; + int m_selected_layers_range_idx {-1}; Clipboard m_clipboard; @@ -147,23 +140,6 @@ private: } m_dragged_data; wxBoxSizer *m_sizer {nullptr}; - wxWindow *m_parent {nullptr}; - - ScalableBitmap m_bmp_modifiermesh; - ScalableBitmap m_bmp_solidmesh; - ScalableBitmap m_bmp_support_enforcer; - ScalableBitmap m_bmp_support_blocker; - ScalableBitmap m_bmp_manifold_warning; - ScalableBitmap m_bmp_cog; - - MenuWithSeparators m_menu_object; - MenuWithSeparators m_menu_part; - MenuWithSeparators m_menu_sla_object; - MenuWithSeparators m_menu_instance; - MenuWithSeparators m_menu_layer; - MenuWithSeparators m_menu_default; - wxMenuItem* m_menu_item_settings { nullptr }; - wxMenuItem* m_menu_item_split_instances { nullptr }; ObjectDataViewModel *m_objects_model{ nullptr }; ModelConfig *m_config {nullptr}; @@ -185,7 +161,6 @@ private: // update_settings_items - updating canvas selection is undesirable, // because it would turn off the gizmos (mainly a problem for the SLA gizmo) - int m_selected_row = 0; wxDataViewItem m_last_selected_item {nullptr}; #ifdef __WXMSW__ // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. @@ -193,8 +168,8 @@ private: #endif /* __MSW__ */ #if 0 - SettingsBundle m_freq_settings_fff; - SettingsBundle m_freq_settings_sla; + SettingsFactory::Bundle m_freq_settings_fff; + SettingsFactory::Bundle m_freq_settings_sla; #endif size_t m_items_count { size_t(-1) }; @@ -212,8 +187,6 @@ public: void set_min_height(); void update_min_height(); - std::map CATEGORY_ICON; - ObjectDataViewModel* GetModel() const { return m_objects_model; } ModelConfig* config() const { return m_config; } std::vector* objects() const { return m_objects; } @@ -221,7 +194,6 @@ public: ModelObject* object(const int obj_idx) const ; void create_objects_ctrl(); - void create_popup_menus(); void update_objects_list_extruder_column(size_t extruders_count); void update_extruder_colors(); // show/hide "Extruder" column for Objects List @@ -232,9 +204,6 @@ public: void update_name_in_model(const wxDataViewItem& item) const; void update_extruder_values_for_items(const size_t max_extruder); - void init_icons(); - void msw_rescale_icons(); - // Get obj_idx and vol_idx values for the selected (by default) or an adjusted item void get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& item = wxDataViewItem(0)); void get_selection_indexes(std::vector& obj_idxs, std::vector& vol_idxs); @@ -264,39 +233,11 @@ public: void increase_instances(); void decrease_instances(); - void get_settings_choice(const wxString& category_name); - void get_freq_settings_choice(const wxString& bundle_name); + void add_category_to_settings_from_selection(const std::vector< std::pair >& category_options, wxDataViewItem item); + void add_category_to_settings_from_frequent(const std::vector& category_options, wxDataViewItem item); void show_settings(const wxDataViewItem settings_item); bool is_instance_or_object_selected(); - wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); - void append_menu_items_add_volume(wxMenu* menu); - wxMenuItem* append_menu_item_split(wxMenu* menu); - wxMenuItem* append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent); - wxMenuItem* append_menu_item_settings(wxMenu* menu); - wxMenuItem* append_menu_item_change_type(wxMenu* menu, wxWindow* parent = nullptr); - wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); - wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent); - void append_menu_items_osx(wxMenu* menu); - wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); - void append_menu_item_export_stl(wxMenu* menu) const; - void append_menu_item_reload_from_disk(wxMenu* menu) const; - void append_menu_item_change_extruder(wxMenu* menu); - void append_menu_item_delete(wxMenu* menu); - void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); - void append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk" - void append_menu_item_merge_to_multipart_object(wxMenu *menu); - void append_menu_item_merge_to_single_object(wxMenu *menu); - void create_object_popupmenu(wxMenu *menu); - void create_sla_object_popupmenu(wxMenu*menu); - void create_part_popupmenu(wxMenu*menu); - void create_instance_popupmenu(wxMenu*menu); - void create_default_popupmenu(wxMenu *menu); - wxMenu* create_settings_popupmenu(wxMenu *parent_menu); - void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true); - - void update_opt_keys(t_config_option_keys& t_optopt_keys, const bool is_object); - void load_subobject(ModelVolumeType type); void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); @@ -318,7 +259,7 @@ public: DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); - bool is_splittable(); + bool is_splittable(bool to_objects); bool selected_instances_of_same_object(); bool can_split_instances(); bool can_merge_to_multipart_object() const; @@ -328,7 +269,6 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; ModelConfig& get_item_config(const wxDataViewItem& item) const; - SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings); void changed_object(const int obj_idx = -1) const; void part_selection_changed(); @@ -404,11 +344,9 @@ public: void change_part_type(); void last_volume_is_deleted(const int obj_idx); - void update_settings_items(); void update_and_show_object_settings_item(); void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); void update_object_list_by_printer_technology(); - void update_object_menu(); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); void instances_to_separated_objects(const int obj_idx); @@ -433,7 +371,7 @@ public: void update_printable_state(int obj_idx, int instance_idx); void toggle_printable_state(wxDataViewItem item); - void show_multi_selection_menu(); + void set_extruder_for_selected_items(const int extruder) const ; private: #ifdef __WXOSX__ @@ -454,11 +392,6 @@ private: #endif /* __WXMSW__ */ void OnEditingDone(wxDataViewEvent &event); void extruder_selection(); - void set_extruder_for_selected_items(const int extruder) const ; - - std::vector get_options(const bool is_part); - const std::vector& get_options_for_bundle(const wxString& bundle_name); - void get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part); }; diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 2501ea499..e6689b43c 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -1,5 +1,6 @@ #include "GUI_ObjectSettings.hpp" #include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "OptionsGroup.hpp" #include "GUI_App.hpp" @@ -83,7 +84,7 @@ bool ObjectSettings::update_settings_list() return false; const bool is_object_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itObject; - SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(&config->get(), is_object_settings); + SettingsFactory::Bundle cat_options = SettingsFactory::get_bundle(&config->get(), is_object_settings); if (!cat_options.empty()) { diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index e9d4efcba..f50c7e356 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -562,8 +562,6 @@ void MainFrame::init_tabpanel() wxGetApp().plater_ = m_plater; - wxGetApp().obj_list()->create_popup_menus(); - if (wxGetApp().is_editor()) create_preset_tabs(); diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 7de37fb48..80655352b 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -2,7 +2,7 @@ #include "wxExtensions.hpp" #include "BitmapCache.hpp" #include "GUI_App.hpp" -#include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "I18N.hpp" #include "libslic3r/Model.hpp" @@ -44,8 +44,9 @@ void ObjectDataViewModelNode::init_container() #endif //__WXGTK__ } -#define LAYER_ROOT_ICON "edit_layers_all" -#define LAYER_ICON "edit_layers_some" +static constexpr char LayerRootIcon[] = "edit_layers_all"; +static constexpr char LayerIcon[] = "edit_layers_some"; +static constexpr char WarningIcon[] = "exclamation"; ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : m_parent(parent), @@ -65,7 +66,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } else if (type == itLayerRoot) { - m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(LayerRootIcon); // FIXME: pass window ptr m_name = _(L("Layers")); } @@ -94,7 +95,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; - m_bmp = create_scaled_bitmap(LAYER_ICON); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(LayerIcon); // FIXME: pass window ptr set_action_and_extruder_icons(); init_container(); @@ -140,17 +141,14 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() { m_bmp = m_empty_bmp; - std::map& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON; - std::string scaled_bitmap_name = m_name.ToUTF8().data(); scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : ""); wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); if (bmp == nullptr) { std::vector bmps; - for (auto& cat : m_opt_categories) - bmps.emplace_back( categories_icon.find(cat) == categories_icon.end() ? - wxNullBitmap : categories_icon.at(cat)); + for (auto& category : m_opt_categories) + bmps.emplace_back(SettingsFactory::get_category_bitmap(category)); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } @@ -249,6 +247,9 @@ static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType roo ObjectDataViewModel::ObjectDataViewModel() { m_bitmap_cache = new Slic3r::GUI::BitmapCache; + + m_volume_bmps = MenuFactory::get_volume_bitmaps(); + m_warning_bmp = create_scaled_bitmap(WarningIcon); } ObjectDataViewModel::~ObjectDataViewModel() @@ -267,7 +268,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, auto root = new ObjectDataViewModelNode(name, extruder_str); // Add error icon if detected auto-repaire if (has_errors) - root->m_bmp = *m_warning_bmp; + root->m_bmp = m_warning_bmp; m_objects.push_back(root); // notify control @@ -317,7 +318,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent // if part with errors is added, but object wasn't marked, then mark it if (!obj_errors && has_errors) - root->SetBitmap(*m_warning_bmp); + root->SetBitmap(m_warning_bmp); // notify control const wxDataViewItem child((void*)node); @@ -1434,10 +1435,20 @@ void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r return; ObjectDataViewModelNode *node = static_cast(item.GetID()); - node->SetBitmap(*m_volume_bmps[int(type)]); + node->SetVolumeType(type); + node->SetBitmap(m_volume_bmps[int(type)]); ItemChanged(item); } +ModelVolumeType ObjectDataViewModel::GetVolumeType(const wxDataViewItem& item) +{ + if (!item.IsOk() || GetItemType(item) != itVolume) + return ModelVolumeType::INVALID; + + ObjectDataViewModelNode *node = static_cast(item.GetID()); + return node->GetVolumeType(); +} + wxDataViewItem ObjectDataViewModel::SetPrintableState( PrintIndicator printable, int obj_idx, @@ -1480,6 +1491,9 @@ wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( void ObjectDataViewModel::Rescale() { + m_volume_bmps = MenuFactory::get_volume_bitmaps(); + m_warning_bmp = create_scaled_bitmap(WarningIcon); + wxDataViewItemArray all_items; GetAllChildren(wxDataViewItem(0), all_items); @@ -1494,15 +1508,15 @@ void ObjectDataViewModel::Rescale() switch (node->m_type) { case itObject: - if (node->m_bmp.IsOk()) node->m_bmp = *m_warning_bmp; + if (node->m_bmp.IsOk()) node->m_bmp = m_warning_bmp; break; case itVolume: node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); break; case itLayerRoot: - node->m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); + node->m_bmp = create_scaled_bitmap(LayerRootIcon); case itLayer: - node->m_bmp = create_scaled_bitmap(LAYER_ICON); + node->m_bmp = create_scaled_bitmap(LayerIcon); break; default: break; } @@ -1514,7 +1528,7 @@ void ObjectDataViewModel::Rescale() wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) { if (!is_marked) - return *m_volume_bmps[static_cast(vol_type)]; + return m_volume_bmps[static_cast(vol_type)]; std::string scaled_bitmap_name = "warning" + std::to_string(static_cast(vol_type)); scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); @@ -1523,8 +1537,8 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty if (bmp == nullptr) { std::vector bmps; - bmps.emplace_back(*m_warning_bmp); - bmps.emplace_back(*m_volume_bmps[static_cast(vol_type)]); + bmps.emplace_back(m_warning_bmp); + bmps.emplace_back(m_volume_bmps[static_cast(vol_type)]); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } @@ -1543,7 +1557,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo return; if (node->GetType() & itVolume) { - node->SetBitmap(*m_volume_bmps[static_cast(node->volume_type())]); + node->SetBitmap(m_volume_bmps[static_cast(node->volume_type())]); return; } diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 17ad2047f..c23ec195b 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -171,13 +171,14 @@ public: } bool SetValue(const wxVariant &variant, unsigned int col); - + void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } void SetIdx(const int& idx); int GetIdx() const { return m_idx; } + ModelVolumeType GetVolumeType() { return m_volume_type; } t_layer_height_range GetLayerRange() const { return m_layer_range; } PrintIndicator IsPrintable() const { return m_printable; } @@ -241,8 +242,8 @@ wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); class ObjectDataViewModel :public wxDataViewModel { std::vector m_objects; - std::vector m_volume_bmps; - wxBitmap* m_warning_bmp { nullptr }; + std::vector m_volume_bmps; + wxBitmap m_warning_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -348,9 +349,8 @@ public: void UpdateObjectPrintable(wxDataViewItem parent_item); void UpdateInstancesPrintable(wxDataViewItem parent_item); - void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } - void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); + ModelVolumeType GetVolumeType(const wxDataViewItem &item); wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, int subobj_idx = -1, ItemType subobj_type = itInstance); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 493958589..15e89168a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -50,6 +50,7 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" +#include "GUI_Factories.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" #include "format.hpp" @@ -1323,7 +1324,7 @@ void Sidebar::update_mode() p->object_list->unselect_objects(); p->object_list->update_selections(); - p->object_list->update_object_menu(); +// p->object_list->update_object_menu(); Layout(); } @@ -1405,23 +1406,7 @@ struct Plater::priv Plater *q; MainFrame *main_frame; - // Object popup menu - MenuWithSeparators object_menu; - // Part popup menu - MenuWithSeparators part_menu; - // SLA-Object popup menu - MenuWithSeparators sla_object_menu; - // Default popup menu (when nothing is selected on 3DScene) - MenuWithSeparators default_menu; - - // Removed/Prepended Items according to the view mode - std::vector items_increase; - std::vector items_decrease; - std::vector items_set_number_of_copies; - enum MenuIdentifier { - miObjectFFF=0, - miObjectSLA - }; + MenuFactory menus; // Data Slic3r::DynamicPrintConfig *config; // FIXME: leak? @@ -1670,7 +1655,6 @@ struct Plater::priv void on_update_geometry(Vec3dsEvent<2>&); void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); - void update_object_menu(); void show_action_buttons(const bool is_ready_to_slice) const; // Set the bed shape to a single closed 2D polygon(array of two element arrays), @@ -1691,12 +1675,11 @@ struct Plater::priv bool can_set_instance_to_object() const; bool can_mirror() const; bool can_reload_from_disk() const; + bool can_split(bool to_objects) const; void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); - void msw_rescale_object_menu(); - void bring_instance_forward() const; // returns the path to project file with the given extension (none if extension == wxEmptyString) @@ -1713,13 +1696,6 @@ struct Plater::priv bool inside_snapshot_capture() { return m_prevent_snapshots != 0; } bool process_completed_with_error { false }; private: - bool init_object_menu(); - bool init_common_menu(wxMenu* menu, const bool is_part = false); - bool complit_init_object_menu(); - bool complit_init_sla_object_menu(); - bool complit_init_part_menu(); - - bool can_split() const; bool layers_height_allowed() const; void update_fff_scene(); @@ -1829,7 +1805,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) hsizer->Add(sidebar, 0, wxEXPAND | wxLEFT | wxRIGHT, 0); q->SetSizer(hsizer); - init_object_menu(); + menus.init(q); // Events: @@ -2725,19 +2701,19 @@ void Plater::priv::split_object() Model new_model = model; ModelObject* current_model_object = new_model.objects[obj_idx]; - if (current_model_object->volumes.size() > 1) - { - Slic3r::GUI::warning_catcher(q, _L("The selected object can't be split because it contains more than one volume/material.")); - return; - } - wxBusyCursor wait; ModelObjectPtrs new_objects; current_model_object->split(&new_objects); if (new_objects.size() == 1) - Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one part.")); + // #ysFIXME use notification + Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one solid part.")); else { + if (current_model_object->volumes.size() != new_objects.size()) + notification_manager->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::RegularNotification, + _u8L("All non-solid parts (modifiers) was deleted")); + Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); remove(obj_idx); @@ -3732,70 +3708,25 @@ void Plater::priv::on_right_click(RBtnEvent& evt) wxMenu* menu = nullptr; - if (obj_idx == -1) // no one or several object are selected - { + if (obj_idx == -1) { // no one or several object are selected if (evt.data.second) // right button was clicked on empty space - menu = &default_menu; + menu = menus.default_menu(); else - { - sidebar->obj_list()->show_multi_selection_menu(); - return; - } + menu = menus.multi_selection_menu(); } - else - { + else { // If in 3DScene is(are) selected volume(s), but right button was clicked on empty space if (evt.data.second) - return; - - int menu_item_convert_unit_position = 11; + return; if (printer_technology == ptSLA) - menu = &sla_object_menu; - else - { + menu = menus.sla_object_menu(); + else { // show "Object menu" for each one or several FullInstance instead of FullObject const bool is_some_full_instances = get_selection().is_single_full_instance() || get_selection().is_single_full_object() || get_selection().is_multiple_full_instance(); - menu = is_some_full_instances ? &object_menu : &part_menu; - if (!is_some_full_instances) - menu_item_convert_unit_position = 2; - } - - sidebar->obj_list()->append_menu_items_convert_unit(menu, menu_item_convert_unit_position); - sidebar->obj_list()->append_menu_item_settings(menu); - - if (printer_technology != ptSLA) - sidebar->obj_list()->append_menu_item_change_extruder(menu); - - if (menu != &part_menu) - { - /* Remove/Prepend "increase/decrease instances" menu items according to the view mode. - * Suppress to show those items for a Simple mode - */ - const MenuIdentifier id = printer_technology == ptSLA ? miObjectSLA : miObjectFFF; - if (wxGetApp().get_mode() == comSimple) { - if (menu->FindItem(_L("Add instance")) != wxNOT_FOUND) - { - /* Detach an items from the menu, but don't delete them - * so that they can be added back later - * (after switching to the Advanced/Expert mode) - */ - menu->Remove(items_increase[id]); - menu->Remove(items_decrease[id]); - menu->Remove(items_set_number_of_copies[id]); - } - } - else { - if (menu->FindItem(_L("Add instance")) == wxNOT_FOUND) - { - // Prepend items to the menu, if those aren't not there - menu->Prepend(items_set_number_of_copies[id]); - menu->Prepend(items_decrease[id]); - menu->Prepend(items_increase[id]); - } - } + menu = is_some_full_instances ? menus.object_menu() : menus.part_menu(); } } @@ -3842,26 +3773,6 @@ void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&) } } -bool Plater::priv::init_object_menu() -{ - items_increase.reserve(2); - items_decrease.reserve(2); - items_set_number_of_copies.reserve(2); - - init_common_menu(&object_menu); - complit_init_object_menu(); - - init_common_menu(&sla_object_menu); - complit_init_sla_object_menu(); - - init_common_menu(&part_menu, true); - complit_init_part_menu(); - - sidebar->obj_list()->create_default_popupmenu(&default_menu); - - return true; -} - void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) { view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background); @@ -3880,12 +3791,6 @@ void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& } } -void Plater::priv::msw_rescale_object_menu() -{ - for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu }) - msw_rescale_menu(dynamic_cast(menu)); -} - wxString Plater::priv::get_project_filename(const wxString& extension) const { return m_project_filename.empty() ? "" : m_project_filename + extension; @@ -3914,144 +3819,6 @@ void Plater::priv::set_project_filename(const wxString& filename) wxGetApp().mainframe->add_to_recent_projects(filename); } -bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) -{ - if (is_part) { - append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), - [this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q); - - append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected volumes from disk"), - [this](wxCommandEvent&) { q->reload_from_disk(); }, "", menu, [this]() { return can_reload_from_disk(); }, q); - - sidebar->obj_list()->append_menu_item_export_stl(menu); - } - else { - wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _L("Add instance") + "\t+", _L("Add one more instance of the selected object"), - [this](wxCommandEvent&) { q->increase_instances(); }, "add_copies", nullptr, [this]() { return can_increase_instances(); }, q); - wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _L("Remove instance") + "\t-", _L("Remove one instance of the selected object"), - [this](wxCommandEvent&) { q->decrease_instances(); }, "remove_copies", nullptr, [this]() { return can_decrease_instances(); }, q); - wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"), - [this](wxCommandEvent&) { q->set_number_of_copies(); }, "number_of_copies", nullptr, [this]() { return can_increase_instances(); }, q); - append_menu_item(menu, wxID_ANY, _L("Fill bed with instances") + dots, _L("Fill the remaining area of bed with instances of the selected object"), - [this](wxCommandEvent&) { q->fill_bed_with_instances(); }, "", nullptr, [this]() { return can_increase_instances(); }, q); - - - items_increase.push_back(item_increase); - items_decrease.push_back(item_decrease); - items_set_number_of_copies.push_back(item_set_number_of_copies); - - // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake. - append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), - [this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q); - - menu->AppendSeparator(); - sidebar->obj_list()->append_menu_item_instance_to_object(menu, q); - menu->AppendSeparator(); - - wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q); - menu->AppendSeparator(); - - append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected object from disk"), - [this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q); - - append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, _L("Export the selected object as STL file"), - [this](wxCommandEvent&) { q->export_stl(false, true); }, "", nullptr, - [this]() { - const Selection& selection = get_selection(); - return selection.is_single_full_instance() || selection.is_single_full_object(); - }, q); - - menu->AppendSeparator(); - - // "Scale to print volume" makes a sense just for whole object - sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu); - - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { - const Selection& selection = get_selection(); - int instance_idx = selection.get_instance_idx(); - evt.Enable(selection.is_single_full_instance() || selection.is_single_full_object()); - if (instance_idx != -1) - { - evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable); - view3D->set_as_dirty(); - } - }, menu_item_printable->GetId()); - } - - sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); - - wxMenu* mirror_menu = new wxMenu(); - if (mirror_menu == nullptr) - return false; - - append_menu_item(mirror_menu, wxID_ANY, _L("Along X axis"), _L("Mirror the selected object along the X axis"), - [this](wxCommandEvent&) { mirror(X); }, "mark_X", menu); - append_menu_item(mirror_menu, wxID_ANY, _L("Along Y axis"), _L("Mirror the selected object along the Y axis"), - [this](wxCommandEvent&) { mirror(Y); }, "mark_Y", menu); - append_menu_item(mirror_menu, wxID_ANY, _L("Along Z axis"), _L("Mirror the selected object along the Z axis"), - [this](wxCommandEvent&) { mirror(Z); }, "mark_Z", menu); - - append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror the selected object"), "", - [this]() { return can_mirror(); }, q); - - return true; -} - -bool Plater::priv::complit_init_object_menu() -{ - wxMenu* split_menu = new wxMenu(); - if (split_menu == nullptr) - return false; - - append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"), - [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", &object_menu, [this]() { return can_split(); }, q); - append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual sub-parts"), - [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu, [this]() { return can_split(); }, q); - - append_submenu(&object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "", - [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); - object_menu.AppendSeparator(); - - // Layers Editing for object - sidebar->obj_list()->append_menu_item_layers_editing(&object_menu, q); - object_menu.AppendSeparator(); - - // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume() - - return true; -} - -bool Plater::priv::complit_init_sla_object_menu() -{ - append_menu_item(&sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"), - [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", nullptr, [this]() { return can_split(); }, q); - - sla_object_menu.AppendSeparator(); - - // Add the automatic rotation sub-menu - append_menu_item( - &sla_object_menu, wxID_ANY, _(L("Optimize orientation")), - _(L("Optimize the rotation of the object for better print results.")), - [this](wxCommandEvent &) { - m_ui_jobs.optimize_rotation(); - }); - - return true; -} - -bool Plater::priv::complit_init_part_menu() -{ - append_menu_item(&part_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual sub-parts"), - [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", nullptr, [this]() { return can_split(); }, q); - - part_menu.AppendSeparator(); - - auto obj_list = sidebar->obj_list(); - obj_list->append_menu_item_change_type(&part_menu, q); - - return true; -} - void Plater::priv::set_current_canvas_as_dirty() { if (current_panel == view3D) @@ -4201,9 +3968,9 @@ bool Plater::priv::can_set_instance_to_object() const return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); } -bool Plater::priv::can_split() const +bool Plater::priv::can_split(bool to_objects) const { - return sidebar->obj_list()->is_splittable(); + return sidebar->obj_list()->is_splittable(to_objects); } bool Plater::priv::layers_height_allowed() const @@ -4319,12 +4086,12 @@ bool Plater::priv::can_decrease_instances() const bool Plater::priv::can_split_to_objects() const { - return can_split(); + return q->can_split(true); } bool Plater::priv::can_split_to_volumes() const { - return (printer_technology != ptSLA) && can_split(); + return (printer_technology != ptSLA) && q->can_split(false); } bool Plater::priv::can_arrange() const @@ -4337,11 +4104,6 @@ bool Plater::priv::can_layers_editing() const return layers_height_allowed(); } -void Plater::priv::update_object_menu() -{ - sidebar->obj_list()->append_menu_items_add_volume(&object_menu); -} - void Plater::priv::show_action_buttons(const bool ready_to_slice) const { // Cache this value, so that the callbacks from the RemovableDriveManager may repeat that value when calling show_action_buttons(). @@ -6061,9 +5823,12 @@ void Plater::suppress_background_process(const bool stop_background_process) } void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } - -void Plater::update_object_menu() { p->update_object_menu(); } -void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); } +void Plater::mirror(Axis axis) { p->mirror(axis); } +void Plater::split_object() { p->split_object(); } +void Plater::split_volume() { p->split_volume(); } +void Plater::optimize_rotation() { p->m_ui_jobs.optimize_rotation();} +void Plater::update_object_menu() { p->menus.update_object_menu(); } +void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); } void Plater::copy_selection_to_clipboard() { @@ -6120,7 +5885,7 @@ void Plater::msw_rescale() p->sidebar->msw_rescale(); - p->msw_rescale_object_menu(); + p->menus.msw_rescale(); Layout(); GetParent()->Layout(); @@ -6132,7 +5897,7 @@ void Plater::sys_color_changed() p->sidebar->sys_color_changed(); // msw_rescale_menu updates just icons, so use it - p->msw_rescale_object_menu(); + p->menus.msw_rescale(); Layout(); GetParent()->Layout(); @@ -6297,6 +6062,8 @@ bool Plater::can_copy_to_clipboard() const bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); } bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } +bool Plater::can_mirror() const { return p->can_mirror(); } +bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } @@ -6338,6 +6105,15 @@ void Plater::bring_instance_forward() p->bring_instance_forward(); } +wxMenu* Plater::object_menu() { return p->menus.object_menu(); } +wxMenu* Plater::part_menu() { return p->menus.part_menu(); } +wxMenu* Plater::sla_object_menu() { return p->menus.sla_object_menu(); } +wxMenu* Plater::default_menu() { return p->menus.default_menu(); } +wxMenu* Plater::instance_menu() { return p->menus.instance_menu(); } +wxMenu* Plater::layer_menu() { return p->menus.layer_menu(); } +wxMenu* Plater::multi_selection_menu() { return p->menus.multi_selection_menu(); } + + SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : m_was_scheduled(wxGetApp().plater()->is_background_process_update_scheduled()) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 82afdcc3d..ff81dad26 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -271,6 +271,10 @@ public: void copy_selection_to_clipboard(); void paste_from_clipboard(); void search(bool plater_is_active); + void mirror(Axis axis); + void split_object(); + void split_volume(); + void optimize_rotation(); bool can_delete() const; bool can_delete_all() const; @@ -287,6 +291,8 @@ public: bool can_undo() const; bool can_redo() const; bool can_reload_from_disk() const; + bool can_mirror() const; + bool can_split(bool to_objects) const; void msw_rescale(); void sys_color_changed(); @@ -375,6 +381,15 @@ public: bool PopupMenu(wxMenu *menu, const wxPoint& pos = wxDefaultPosition); bool PopupMenu(wxMenu *menu, int x, int y) { return this->PopupMenu(menu, wxPoint(x, y)); } + // get same Plater/ObjectList menus + wxMenu* object_menu(); + wxMenu* part_menu(); + wxMenu* sla_object_menu(); + wxMenu* default_menu(); + wxMenu* instance_menu(); + wxMenu* layer_menu(); + wxMenu* multi_selection_menu(); + private: struct priv; std::unique_ptr p;