Ys code refactoring (#6227)

* 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
This commit is contained in:
Oleksandra Yushchenko 2021-03-15 10:04:45 +01:00 committed by GitHub
parent 95c5763b83
commit e002f0066f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1376 additions and 1415 deletions

View File

@ -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

View File

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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
#ifndef slic3r_GUI_Factories_hpp_
#define slic3r_GUI_Factories_hpp_
#include <map>
#include <vector>
#include <array>
#include <wx/bitmap.h>
#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<std::string, std::vector<std::string>> Bundle;
static std::map<std::string, std::string> 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<std::string> get_options(bool is_part);
};
class MenuFactory
{
public:
static std::vector<std::pair<std::string, std::string>> ADD_VOLUME_MENU_ITEMS;
static std::vector<wxBitmap> 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<wxMenuItem*, mtCount> items_increase;
std::array<wxMenuItem*, mtCount> items_decrease;
std::array<wxMenuItem*, mtCount> 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_

View File

@ -260,16 +260,15 @@ void ObjectLayers::msw_rescale()
editor->msw_rescale();
}
const std::vector<size_t> 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<PlusMinusButton*>(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<PlusMinusButton*>(b_item->GetWindow());
if (button != nullptr)
button->msw_rescale();
}
}
}
}
m_grid_sizer->Layout();

File diff suppressed because it is too large Load Diff

View File

@ -31,18 +31,11 @@ class TriangleMesh;
enum class ModelVolumeType : int;
// FIXME: broken build on mac os because of this is missing:
typedef std::vector<std::string> t_config_option_keys;
typedef std::map<std::string, std::vector<std::string>> SettingsBundle;
// category -> vector ( option ; label )
typedef std::map< std::string, std::vector< std::pair<std::string, std::string> > > settings_menu_hierarchy;
typedef std::vector<ModelVolume*> ModelVolumePtrs;
typedef double coordf_t;
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
typedef std::map<t_layer_height_range, ModelConfig> t_layer_config_ranges;
typedef std::vector<std::string> t_config_option_keys;
typedef std::vector<ModelVolume*> ModelVolumePtrs;
typedef double coordf_t;
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
typedef std::map<t_layer_height_range, ModelConfig> 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<std::string, wxBitmap> CATEGORY_ICON;
ObjectDataViewModel* GetModel() const { return m_objects_model; }
ModelConfig* config() const { return m_config; }
std::vector<ModelObject*>* 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<int>& obj_idxs, std::vector<int>& 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<std::string, bool> >& category_options, wxDataViewItem item);
void add_category_to_settings_from_frequent(const std::vector<std::string>& 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<std::pair<wxString, bool>> &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<int>& 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<std::string> get_options(const bool is_part);
const std::vector<std::string>& get_options_for_bundle(const wxString& bundle_name);
void get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part);
};

View File

@ -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())
{

View File

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

View File

@ -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<std::string, wxBitmap>& 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<wxBitmap> 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<ObjectDataViewModelNode*>(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<ObjectDataViewModelNode*>(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<int>(vol_type)];
return m_volume_bmps[static_cast<int>(vol_type)];
std::string scaled_bitmap_name = "warning" + std::to_string(static_cast<int>(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<wxBitmap> bmps;
bmps.emplace_back(*m_warning_bmp);
bmps.emplace_back(*m_volume_bmps[static_cast<int>(vol_type)]);
bmps.emplace_back(m_warning_bmp);
bmps.emplace_back(m_volume_bmps[static_cast<int>(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<int>(node->volume_type())]);
node->SetBitmap(m_volume_bmps[static_cast<int>(node->volume_type())]);
return;
}

View File

@ -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<ObjectDataViewModelNode*> m_objects;
std::vector<wxBitmap*> m_volume_bmps;
wxBitmap* m_warning_bmp { nullptr };
std::vector<wxBitmap> 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<wxBitmap*>& 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);

View File

@ -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<wxMenuItem*> items_increase;
std::vector<wxMenuItem*> items_decrease;
std::vector<wxMenuItem*> 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<wxMenu*>(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())
{

View File

@ -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<priv> p;