mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-30 04:02:52 -06:00
* GUI_ObjectList code refactoring:
The MenuFactory structure contains functions related to the context menu and bitmaps used to different volume types.
The SettingsFactory structure contains functions to getting overridden options, its bundles and bitmaps used to setting categories.
Fixed bugs/crashes:
1. Add object -> Add Settings from 3D scene -> Right click on object => Part's Settings list instead of object's
(Same behavior if something else but Object is selected in ObjectList)
2. Add settings to the part -> Change part type to the "Support Blocker/Enforcer" -> Settings disappears (it's OK) but =>
Save Project -> Open project => Support Blocker/Enforcer has a settings
3. Add part for object -> Change type of part -> Change monitor DPI -> old type icon appears
4. Select all instances in ObjectList -> Context menu in 3D scene -> Add Settings -> Select some category -> Crash
* ObjectLayers: Fixed a crash on re-scaling, when some layer range is selected
* Fixed OSX build
* Added menu item "Split to Objects" for multipart objects
+ Fixed bug: Add 2 parts,
Add some settings for one part
Delete part without settings => Single part object without settings, but settings are applied for the object.
+ Next refactoring: use same menu for Plater and ObjectList
1027 lines
40 KiB
C++
1027 lines
40 KiB
C++
#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 <boost/algorithm/string.hpp>
|
|
#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<std::string> 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<std::string> 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<std::string, std::string> 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<std::pair<std::string, std::string>> 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<std::string, std::string> > > 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<std::string, std::string> option_label(option, label);
|
|
std::vector< std::pair<std::string, std::string> > new_category;
|
|
auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category);
|
|
cat_opt_label.push_back(option_label);
|
|
if (cat_opt_label.size() == 1)
|
|
settings_menu[category] = cat_opt_label;
|
|
}
|
|
}
|
|
|
|
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<std::string, bool> > 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<std::string, std::string>& 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<std::string> tmp_freq_cat = {};
|
|
|
|
for (auto& cat : freq_settings)
|
|
{
|
|
if (_(cat.first) == category_name)
|
|
{
|
|
std::vector<std::string>& freq_settings_category = cat.second;
|
|
freq_settings_category.clear();
|
|
freq_settings_category.reserve(selection_cnt);
|
|
for (auto sel : selections)
|
|
freq_settings_category.push_back((*settings_list)[sel].first);
|
|
|
|
changed_existing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!changed_existing)
|
|
{
|
|
// Create new "Quick menu" item
|
|
for (auto& cat : settings_menu)
|
|
{
|
|
if (_(cat.first) == category_name)
|
|
{
|
|
freq_settings[cat.first] = std::vector<std::string>{};
|
|
|
|
std::vector<std::string>& freq_settings_category = freq_settings.find(cat.first)->second;
|
|
freq_settings_category.reserve(selection_cnt);
|
|
for (auto sel : selections)
|
|
freq_settings_category.push_back((*settings_list)[sel].first);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
};
|
|
|
|
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<std::string, bool> > 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<std::string> 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<std::string> 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<wxBitmap> MenuFactory::get_volume_bitmaps()
|
|
{
|
|
std::vector<wxBitmap> 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<MenuWithSeparators*>(menu_);
|
|
|
|
const wxString menu_name = _L("Add settings");
|
|
// Delete old items from settings popupmenu
|
|
auto settings_id = menu->FindItem(menu_name);
|
|
if (settings_id != wxNOT_FOUND)
|
|
menu->Destroy(settings_id);
|
|
|
|
for (auto& it : FREQ_SETTINGS_BUNDLE_FFF)
|
|
{
|
|
settings_id = menu->FindItem(_(it.first));
|
|
if (settings_id != wxNOT_FOUND)
|
|
menu->Destroy(settings_id);
|
|
}
|
|
for (auto& it : FREQ_SETTINGS_BUNDLE_SLA)
|
|
{
|
|
settings_id = menu->FindItem(_(it.first));
|
|
if (settings_id != wxNOT_FOUND)
|
|
menu->Destroy(settings_id);
|
|
}
|
|
#if 0
|
|
for (auto& it : m_freq_settings_fff)
|
|
{
|
|
settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()));
|
|
if (settings_id != wxNOT_FOUND)
|
|
menu->Destroy(settings_id);
|
|
}
|
|
for (auto& it : m_freq_settings_sla)
|
|
{
|
|
settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()));
|
|
if (settings_id != wxNOT_FOUND)
|
|
menu->Destroy(settings_id);
|
|
}
|
|
#endif
|
|
menu->DestroySeparators(); // delete old separators
|
|
|
|
// If there are selected more then one instance but not all of them
|
|
// don't add settings menu items
|
|
const Selection& selection = 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<wxString> names = { _L("Change extruder"), _L("Set extruder for selected items") };
|
|
// Delete old menu item
|
|
for (const wxString& name : names) {
|
|
const int item_id = menu->FindItem(name);
|
|
if (item_id != wxNOT_FOUND)
|
|
menu->Destroy(item_id);
|
|
}
|
|
|
|
const int extruders_cnt = extruders_count();
|
|
if (extruders_cnt <= 1)
|
|
return;
|
|
|
|
wxDataViewItemArray sels;
|
|
obj_list()->GetSelections(sels);
|
|
if (sels.IsEmpty())
|
|
return;
|
|
|
|
std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
|
|
wxMenu* extruder_selection_menu = new wxMenu();
|
|
const wxString& name = sels.Count() == 1 ? names[0] : names[1];
|
|
|
|
int initial_extruder = -1; // negative value for multiple object/part selection
|
|
if (sels.Count() == 1) {
|
|
const ModelConfig& config = 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<int> 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<std::pair<ConversionType, wxString>> 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<wxMenu*>(menu));
|
|
}
|
|
|
|
} //namespace GUI
|
|
} //namespace Slic3r
|