mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-07 15:51:08 +00:00
A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block.
2671 lines
109 KiB
C++
2671 lines
109 KiB
C++
#include "libslic3r/Config.hpp"
|
|
#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 "Shortcuts.hpp"
|
|
#include "I18N.hpp"
|
|
#include "Plater.hpp"
|
|
#include "ObjectDataViewModel.hpp"
|
|
|
|
#include "OptionsGroup.hpp"
|
|
#include "GLCanvas3D.hpp"
|
|
#include "Selection.hpp"
|
|
//BBS: add partplate related logic
|
|
#include "PartPlate.hpp"
|
|
#include "Gizmos/GLGizmoEmboss.hpp"
|
|
#include "Gizmos/GLGizmoSVG.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <string>
|
|
#include <map>
|
|
#include <vector>
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
#include <wx/gdicmn.h>
|
|
#include "slic3r/GUI/wxExtensions.hpp"
|
|
#include <utility>
|
|
#include <wx/dataview.h>
|
|
#include <wx/string.h>
|
|
#include <wx/event.h>
|
|
#include <cstddef>
|
|
#include <boost/filesystem/path.hpp>
|
|
#include "libslic3r/Utils.hpp"
|
|
#include "libslic3r/Format/bbs_3mf.hpp"
|
|
#include "slic3r/GUI/Jobs/Job.hpp"
|
|
#include "slic3r/GUI/Gizmos/GLGizmosManager.hpp"
|
|
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
|
|
#include "slic3r/GUI/GUI.hpp"
|
|
#include "slic3r/GUI/Event.hpp"
|
|
#include <cassert>
|
|
#include "libslic3r/Point.hpp"
|
|
#include <cstdlib>
|
|
#include <boost/log/trivial.hpp>
|
|
#include "slic3r/GUI/Tab.hpp"
|
|
#include "ParamsPanel.hpp"
|
|
#include "MsgDialog.hpp"
|
|
#include "wx/utils.h"
|
|
|
|
namespace Slic3r
|
|
{
|
|
namespace GUI
|
|
{
|
|
|
|
static PrinterTechnology printer_technology()
|
|
{
|
|
return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
|
|
}
|
|
|
|
static int filaments_count()
|
|
{
|
|
return wxGetApp().filaments_cnt();
|
|
}
|
|
|
|
static bool is_improper_category(const std::string& category, const int filaments_cnt, const bool is_object_settings = true)
|
|
{
|
|
return category.empty() ||
|
|
(filaments_cnt == 1 && (category == "Extruders" || category == "Wipe options")) ||
|
|
(!is_object_settings && category == "Support material");
|
|
}
|
|
|
|
|
|
//-------------------------------------
|
|
// SettingsFactory
|
|
//-------------------------------------
|
|
|
|
// pt_FFF
|
|
static SettingsFactory::Bundle FREQ_SETTINGS_BUNDLE_FFF =
|
|
{
|
|
//BBS
|
|
{ L("Quality"), { "layer_height" } },
|
|
{ L("Shell"), { "wall_loops", "top_shell_layers", "bottom_shell_layers"} },
|
|
{ L("Infill") , { "sparse_infill_density", "sparse_infill_pattern" } },
|
|
// BBS
|
|
{ L("Support") , { "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap",
|
|
"support_base_pattern", "support_on_build_plate_only","support_critical_regions_only",
|
|
"support_remove_small_overhang",
|
|
"support_base_pattern_spacing", "support_expansion"}},
|
|
//BBS
|
|
{ L("Flush options") , { "flush_into_infill", "flush_into_objects", "flush_into_support"} }
|
|
};
|
|
|
|
// pt_SLA
|
|
static SettingsFactory::Bundle FREQ_SETTINGS_BUNDLE_SLA =
|
|
{
|
|
// BBS: remove SLA freq settings
|
|
};
|
|
|
|
//BBS: add setting data for table
|
|
std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::OBJECT_CATEGORY_SETTINGS=
|
|
{
|
|
{ L("Quality"), {{"layer_height", "",1},
|
|
//{"initial_layer_print_height", "",2},
|
|
{"seam_position", "",2},
|
|
{"slice_closing_radius", "",3}, {"resolution", "",4},
|
|
{"xy_hole_compensation", "",5}, {"xy_contour_compensation", "",6}, {"elefant_foot_compensation", "",7},
|
|
{"make_overhang_printable_angle","", 8},{"make_overhang_printable_hole_size","",9}, {"wall_sequence","",10},
|
|
{"precise_z_height", "",10}
|
|
}},
|
|
{ L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3},{"brim_flow_ratio", "",4},{"brim_use_efc_outline", "",5},
|
|
{"enable_support", "",6},{"support_type", "",7},{"support_threshold_angle", "",8}, {"support_threshold_overlap", "",9}, {"support_on_build_plate_only", "",10},
|
|
{"support_filament", "",11},{"support_interface_filament", "",12},{"support_expansion", "",13},{"support_style", "",14},
|
|
{"tree_support_brim_width", "",15}, {"tree_support_branch_angle", "",16},{"tree_support_branch_angle_organic","",17}, {"tree_support_wall_count", "",18},{"tree_support_branch_diameter_angle", "",19},//tree support
|
|
{"support_bottom_z_distance", "",20},{"support_top_z_distance", "",21},{"support_base_pattern", "",22},{"support_base_pattern_spacing", "",23},
|
|
{"support_interface_top_layers", "",24},{"support_interface_bottom_layers", "",25},{"support_interface_spacing", "",26},{"support_bottom_interface_spacing", "",27},
|
|
{"support_object_xy_distance", "",28}, {"bridge_no_support", "",29},{"max_bridge_length", "",30},{"support_critical_regions_only", "",31},{"support_remove_small_overhang","",32},
|
|
{"support_object_first_layer_gap","",33}
|
|
}},
|
|
{ L("Speed"), {{"support_speed", "",12}, {"support_interface_speed", "",13}
|
|
}}
|
|
};
|
|
|
|
std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATEGORY_SETTINGS =
|
|
{{L("Quality"),
|
|
{{"ironing_type", "", 8},
|
|
{"ironing_flow", "", 9},
|
|
{"ironing_spacing", "", 10},
|
|
{"ironing_inset", "", 11},
|
|
{"bridge_flow", "", 11},
|
|
{"make_overhang_printable", "", 11},
|
|
{"bridge_density", "", 1},
|
|
{"ironing_expansion", "", 14},
|
|
{"zaa_enabled", "", 1},
|
|
{"zaa_minimize_perimeter_height", "", 2},
|
|
{"zaa_dont_alternate_fill_direction", "", 3},
|
|
{"zaa_min_z", "", 4}}},
|
|
{L("Strength"),
|
|
{{"wall_loops", "", 1},
|
|
{"top_shell_layers", L("Top solid layers"), 1},
|
|
{"top_shell_thickness", L("Top minimum shell thickness"), 1},
|
|
{"top_surface_density", L("Top Surface Density"), 1},
|
|
{"bottom_shell_layers", L("Bottom solid layers"), 1},
|
|
{"bottom_shell_thickness", L("Bottom minimum shell thickness"), 1},
|
|
{"bottom_surface_density", L("Bottom Surface Density"), 1},
|
|
{"sparse_infill_density", "", 1},
|
|
{"fill_multiline", "", 1},
|
|
{"sparse_infill_pattern", "", 1},
|
|
{"sparse_infill_smooth_factor", "", 1},
|
|
{"lateral_lattice_angle_1", "", 1},
|
|
{"lateral_lattice_angle_2", "", 1},
|
|
{"infill_overhang_angle", "", 1},
|
|
{"lightning_overhang_angle", "", 1},
|
|
{"lightning_prune_angle", "", 1},
|
|
{"lightning_straightening_angle", "", 1},
|
|
{"infill_anchor", "", 1},
|
|
{"infill_anchor_max", "", 1},
|
|
{"top_surface_pattern", "", 1},
|
|
{"top_surface_expansion", "", 1},
|
|
{"top_surface_expansion_margin", "", 1},
|
|
{"top_surface_expansion_direction", "", 1},
|
|
{"bottom_surface_pattern", "", 1},
|
|
{"internal_solid_infill_pattern", "", 1},
|
|
{"align_infill_direction_to_model", "", 1},
|
|
{"extra_solid_infills", "", 1},
|
|
{"infill_combination", "", 1},
|
|
{"infill_combination_max_layer_height", "", 1},
|
|
{"infill_wall_overlap", "", 1},
|
|
{"top_bottom_infill_wall_overlap", "", 1},
|
|
{"solid_infill_direction", "", 1},
|
|
{"top_layer_direction", "", 1},
|
|
{"bottom_layer_direction", "", 1},
|
|
{"infill_direction", "", 1},
|
|
{"bridge_angle", "", 1},
|
|
{"internal_bridge_angle", "", 1},
|
|
{"minimum_sparse_infill_area", "", 1}}},
|
|
{L("Speed"),
|
|
{{"outer_wall_speed", "", 1},
|
|
{"inner_wall_speed", "", 2},
|
|
{"sparse_infill_speed", "", 3},
|
|
{"top_surface_speed", "", 4},
|
|
{"internal_solid_infill_speed", "", 5},
|
|
{"enable_overhang_speed", "", 6},
|
|
{"overhang_1_4_speed", "", 7},
|
|
{"overhang_2_4_speed", "", 8},
|
|
{"overhang_3_4_speed", "", 9},
|
|
{"overhang_4_4_speed", "", 10},
|
|
{"bridge_speed", "", 11},
|
|
{"gap_infill_speed", "", 12},
|
|
{"internal_bridge_speed", "", 13}}}};
|
|
|
|
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;
|
|
}
|
|
|
|
std::vector<SimpleSettingData> SettingsFactory::get_visible_options(const std::string& category, const bool is_part)
|
|
{
|
|
/*t_config_option_keys options = {
|
|
//Quality
|
|
"wall_infill_order", "ironing_type", "inner_wall_line_width", "outer_wall_line_width", "top_surface_line_width",
|
|
//Shell
|
|
"wall_loops", "top_shell_layers", "bottom_shell_layers", "top_shell_thickness", "bottom_shell_thickness", "top_surface_expansion", "top_surface_expansion_margin", "top_surface_expansion_direction",
|
|
//Infill
|
|
"sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern", "infill_combination", "infill_direction", "infill_wall_overlap",
|
|
//speed
|
|
"inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed", "top_surface_speed", "gap_infill_speed"
|
|
};
|
|
|
|
t_config_option_keys object_options = {
|
|
//Quality
|
|
"layer_height", "initial_layer_print_height", "adaptive_layer_height", "seam_position", "xy_hole_compensation", "xy_contour_compensation", "elefant_foot_compensation", "support_line_width",
|
|
//Support
|
|
"enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap", "support_on_build_plate_only", "support_critical_regions_only", "enforce_support_layers",
|
|
//tree support
|
|
"tree_support_wall_count",
|
|
//support
|
|
"support_top_z_distance", "support_base_pattern", "support_base_pattern_spacing", "support_interface_top_layers", "support_interface_bottom_layers", "support_interface_spacing", "support_bottom_interface_spacing", "support_object_xy_distance",
|
|
"support_object_first_layer_gap",
|
|
//adhesion
|
|
"brim_type", "brim_width", "brim_object_gap", "raft_layers"
|
|
};*/
|
|
std::vector<SimpleSettingData> options;
|
|
std::map<std::string, std::vector<SimpleSettingData>>::iterator it;
|
|
|
|
it = PART_CATEGORY_SETTINGS.find(category);
|
|
if (it != PART_CATEGORY_SETTINGS.end())
|
|
{
|
|
options = PART_CATEGORY_SETTINGS[category];
|
|
}
|
|
|
|
if (!is_part) {
|
|
it = OBJECT_CATEGORY_SETTINGS.find(category);
|
|
if (it != OBJECT_CATEGORY_SETTINGS.end())
|
|
options.insert(options.end(), OBJECT_CATEGORY_SETTINGS[category].begin(), OBJECT_CATEGORY_SETTINGS[category].end());
|
|
}
|
|
|
|
auto sort_func = [](SimpleSettingData& setting1, SimpleSettingData& setting2) {
|
|
return (setting1.priority < setting2.priority);
|
|
};
|
|
std::sort(options.begin(), options.end(), sort_func);
|
|
return options;
|
|
}
|
|
|
|
std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::get_all_visible_options(const bool is_part)
|
|
{
|
|
std::map<std::string, std::vector<SimpleSettingData>> option_maps;
|
|
std::map<std::string, std::vector<SimpleSettingData>>::iterator it1, it2;
|
|
|
|
option_maps = PART_CATEGORY_SETTINGS;
|
|
if (!is_part) {
|
|
for (it1 = OBJECT_CATEGORY_SETTINGS.begin(); it1 != OBJECT_CATEGORY_SETTINGS.end(); it1++)
|
|
{
|
|
std::string category = it1->first;
|
|
it2 = PART_CATEGORY_SETTINGS.find(category);
|
|
if (it2 != PART_CATEGORY_SETTINGS.end())
|
|
{
|
|
std::vector<SimpleSettingData>& options = option_maps[category];
|
|
options.insert(options.end(), it1->second.begin(), it1->second.end());
|
|
|
|
auto sort_func = [](SimpleSettingData& setting1, SimpleSettingData& setting2) {
|
|
return (setting1.priority < setting2.priority);
|
|
};
|
|
std::sort(options.begin(), options.end(), sort_func);
|
|
}
|
|
else {
|
|
option_maps.insert(*it1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return option_maps;
|
|
}
|
|
|
|
|
|
SettingsFactory::Bundle SettingsFactory::get_bundle(const DynamicPrintConfig* config, bool is_object_settings, bool is_layer_settings/* = false*/)
|
|
{
|
|
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);
|
|
if (is_layer_settings)
|
|
full_current_opts.push_back("layer_height");
|
|
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 filaments_cnt = wxGetApp().filaments_cnt();
|
|
|
|
Bundle bundle;
|
|
for (auto& opt_key : opt_keys)
|
|
{
|
|
auto category = config->def()->get(opt_key)->category;
|
|
if (is_improper_category(category, filaments_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("Quality") , "blank2" },
|
|
{ L("Shell") , "blank_14" },
|
|
{ L("Infill") , "blank_14" },
|
|
{ L("Ironing") , "blank_14" },
|
|
{ L("Fuzzy skin") , "menu_fuzzy_skin" },
|
|
{ L("Support") , "support" },
|
|
{ L("Speed") , "blank_14" },
|
|
{ L("Extruders") , "blank_14" },
|
|
{ L("Extrusion width") , "blank_14" },
|
|
{ L("Wipe options") , "blank_14" },
|
|
{ L("Bed adhesion") , "blank_14" },
|
|
// { L("Speed > Acceleration") , "time" },
|
|
{ L("Advanced") , "blank_14" },
|
|
// BBS: remove SLA categories
|
|
};
|
|
|
|
wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name, bool menu_bmp)
|
|
{
|
|
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
|
|
static const constexpr std::array<std::pair<const char *, const char *>, 11> ADD_VOLUME_MENU_ITEMS = {{
|
|
// menu_item Name menu_item bitmap name
|
|
{L("Add Part"), "menu_add_part" }, // ~ModelVolumeType::MODEL_PART
|
|
{L("Add Negative Part"), "menu_add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME
|
|
{L("Add Modifier"), "menu_add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
|
{L("Add Support Blocker"), "menu_support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER
|
|
{L("Add Support Enforcer"), "menu_support_enforcer"}, // ~ModelVolumeType::SUPPORT_ENFORCER
|
|
// Precise Seam modifiers (all 6 subtypes - only first one shown in menu, others used for tree icons)
|
|
{L("Add Precise Seam"), "menu_precise_seam_center"}, // ~ModelVolumeType::PRECISE_SEAM_CENTER
|
|
{L("Add Precise Seam"), "menu_precise_seam_left"}, // ~ModelVolumeType::PRECISE_SEAM_LEFT
|
|
{L("Add Precise Seam"), "menu_precise_seam_right"}, // ~ModelVolumeType::PRECISE_SEAM_RIGHT
|
|
{L("Add Precise Seam"), "menu_precise_seam_enforced"}, // ~ModelVolumeType::PRECISE_SEAM_ENFORCED
|
|
{L("Add Precise Seam"), "menu_precise_seam_blocked"}, // ~ModelVolumeType::PRECISE_SEAM_BLOCKED
|
|
{L("Add Precise Seam"), "menu_precise_seam_neutral"}, // ~ModelVolumeType::PRECISE_SEAM_NEUTRAL
|
|
}};
|
|
|
|
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
|
|
static const constexpr std::array<std::pair<const char *, const char *>, 3> TEXT_VOLUME_ICONS {{
|
|
// menu_item Name menu_item bitmap name
|
|
{L("Add text"), "add_text_part"}, // ~ModelVolumeType::MODEL_PART
|
|
{L("Add negative text"), "add_text_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME
|
|
{L("Add text modifier"), "add_text_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
|
}};
|
|
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
|
|
static const constexpr std::array<std::pair<const char *, const char *>, 3> SVG_VOLUME_ICONS{{
|
|
{L("Add SVG part"), "svg_part"}, // ~ModelVolumeType::MODEL_PART
|
|
{L("Add negative SVG"), "svg_negative"}, // ~ModelVolumeType::NEGATIVE_VOLUME
|
|
{L("Add SVG modifier"), "svg_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
|
}};
|
|
|
|
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()->get_current_canvas3D(true)->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 filaments_cnt = filaments_count();
|
|
|
|
DynamicPrintConfig config;
|
|
for (auto& option : options)
|
|
{
|
|
auto const opt = config.def()->get(option);
|
|
auto category = opt->category;
|
|
if (is_improper_category(category, filaments_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 settings"), category_name, names) != -1) {
|
|
for (auto sel : selections)
|
|
category_options[sel].second = true;
|
|
}
|
|
return category_options;
|
|
};
|
|
|
|
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 filaments_cnt = filaments_count();
|
|
|
|
for (auto& category : bundle) {
|
|
if (is_improper_category(category.first, filaments_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 == _("Quality")) {
|
|
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());
|
|
}
|
|
}
|
|
|
|
std::vector<wxBitmap> MenuFactory::get_volume_bitmaps()
|
|
{
|
|
std::vector<wxBitmap> volume_bmps;
|
|
volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size());
|
|
for (const auto& item : ADD_VOLUME_MENU_ITEMS) {
|
|
volume_bmps.push_back(create_scaled_bitmap(item.second));
|
|
}
|
|
return volume_bmps;
|
|
}
|
|
|
|
std::vector<wxBitmap> MenuFactory::get_text_volume_bitmaps()
|
|
{
|
|
std::vector<wxBitmap> volume_bmps;
|
|
volume_bmps.reserve(TEXT_VOLUME_ICONS.size());
|
|
for (const auto& item : TEXT_VOLUME_ICONS)
|
|
volume_bmps.push_back(create_scaled_bitmap(item.second));
|
|
return volume_bmps;
|
|
}
|
|
|
|
std::vector<wxBitmap> MenuFactory::get_svg_volume_bitmaps()
|
|
{
|
|
std::vector<wxBitmap> volume_bmps;
|
|
volume_bmps.reserve(SVG_VOLUME_ICONS.size());
|
|
for (const auto &item : SVG_VOLUME_ICONS)
|
|
volume_bmps.push_back(create_scaled_bitmap(item.second));
|
|
return volume_bmps;
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_set_visible(wxMenu* menu)
|
|
{
|
|
bool has_one_shown = false;
|
|
const Selection& selection = plater()->canvas3D()->get_selection();
|
|
for (unsigned int i : selection.get_volume_idxs()) {
|
|
has_one_shown |= selection.get_volume(i)->visible;
|
|
}
|
|
|
|
append_menu_item(menu, wxID_ANY, has_one_shown ?_L("Hide") : _L("Show"), "",
|
|
[has_one_shown](wxCommandEvent&) { plater()->set_selected_visible(!has_one_shown); }, "", nullptr,
|
|
[]() { return true; }, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_delete(wxMenu* menu)
|
|
{
|
|
#ifdef __WINDOWS__
|
|
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L_CONTEXT("Del", "Keyboard Shortcut"), _L("Delete the selected object"),
|
|
[](wxCommandEvent&) { plater()->remove_selected(); }, "menu_delete", nullptr,
|
|
[]() { return plater()->can_delete(); }, m_parent);
|
|
#else
|
|
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L_CONTEXT("Backspace", "Keyboard Shortcut"), _L("Delete the selected object"),
|
|
[](wxCommandEvent&) { plater()->remove_selected(); }, "", nullptr,
|
|
[]() { return plater()->can_delete(); }, m_parent);
|
|
#endif
|
|
}
|
|
|
|
wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) {
|
|
auto sub_menu = new wxMenu;
|
|
|
|
if (type != ModelVolumeType::INVALID) {
|
|
append_menu_item(sub_menu, wxID_ANY, _L("Load..."), "",
|
|
[type](wxCommandEvent&) { obj_list()->load_subobject(type); }, "menu_load", menu);
|
|
sub_menu->AppendSeparator();
|
|
}
|
|
|
|
append_menu_item(sub_menu, wxID_ANY, _L("Cube"), "",
|
|
[type](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Cube") ,type); },"menu_obj_cube", menu);
|
|
|
|
append_menu_item(sub_menu, wxID_ANY, _L("Cylinder"), "",
|
|
[type](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Cylinder"), type); },"menu_obj_cylinder", menu);
|
|
|
|
append_menu_item(sub_menu, wxID_ANY, _L("Sphere"), "",
|
|
[type](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Sphere"), type); },"menu_obj_sphere", menu);
|
|
|
|
append_menu_item(sub_menu, wxID_ANY, _L("Cone"), "",
|
|
[type](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Cone"), type); },"menu_obj_cone", menu);
|
|
|
|
append_menu_item(sub_menu, wxID_ANY, _L("Disc"), "",
|
|
[type](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Disc"), type); },"menu_obj_disc", menu);
|
|
|
|
append_menu_item(sub_menu, wxID_ANY, _L("Torus"), "",
|
|
[type](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Torus"), type); },"menu_obj_torus", menu);
|
|
|
|
|
|
append_menu_item_add_text(sub_menu, type);
|
|
append_menu_item_add_svg(sub_menu, type);
|
|
|
|
return sub_menu;
|
|
}
|
|
|
|
// Orca: handy models shipped under <resources>/handy_models. Defining everything in one table keeps
|
|
// the menu label, the files to load and the per-model behavior in a single place. Labels are wrapped
|
|
// in L() so they are picked up for translation. Shared with the command palette.
|
|
const std::vector<MenuFactory::HandyModel>& MenuFactory::handy_models()
|
|
{
|
|
static const std::vector<HandyModel> models = {
|
|
{"orca_cube", L("Orca Cube"), {"OrcaCube_v2.drc", "OrcaPlug_v2.drc"}, true},
|
|
{"orcasliced_combo", L("OrcaSliced Combo"), {"OrcaSliced.3mf", "OrcaCube_v2.drc", "OrcaPlug_v2.drc"}, true},
|
|
{"orca_badge", L("Orca Badge"), {"OrcaBadge.3mf"}},
|
|
{"orca_tolerance_test", L("Orca Tolerance Test"), {"OrcaToleranceTest.drc"}},
|
|
{"3dbenchy", L("3DBenchy"), {"3DBenchy.drc"}},
|
|
{"cali_cat", L("Cali Cat"), {"calicat.drc"}},
|
|
{"autodesk_fdm_test", L("Autodesk FDM Test"), {"ksr_fdmtest_v4.drc"}},
|
|
{"voron_cube", L("Voron Cube"), {"Voron_Design_Cube_v7.drc"}},
|
|
{"stanford_bunny", L("Stanford Bunny"), {"Stanford_Bunny.drc"}},
|
|
{"orca_string_hell", L("Orca String Hell"), {"Orca_stringhell.drc"}, false, true},
|
|
};
|
|
return models;
|
|
}
|
|
|
|
void MenuFactory::load_handy_model(std::size_t index)
|
|
{
|
|
const std::vector<HandyModel>& models = handy_models();
|
|
if (index >= models.size())
|
|
return;
|
|
const HandyModel& model = models[index];
|
|
|
|
std::vector<boost::filesystem::path> input_files;
|
|
input_files.reserve(model.file_names.size());
|
|
for (const auto& file_name : model.file_names)
|
|
input_files.push_back((boost::filesystem::path(Slic3r::resources_dir()) / "handy_models" / file_name));
|
|
|
|
Plater* pl = plater();
|
|
if (!pl)
|
|
return;
|
|
pl->load_files(input_files, LoadStrategy::LoadModel);
|
|
if (model.arrange_after_import) {
|
|
pl->set_prepare_state(Job::PREPARE_STATE_MENU);
|
|
pl->arrange();
|
|
}
|
|
|
|
// Suggest to change settings for stringhell
|
|
// This serves as mini tutorial for new users
|
|
if (model.is_stringhell) {
|
|
wxGetApp().CallAfter([=] {
|
|
DynamicPrintConfig* m_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
|
|
|
bool is_only_one_wall_top = m_config->opt_bool("only_one_wall_top");
|
|
auto min_width_top_surface = m_config->option<ConfigOptionFloatOrPercent>("min_width_top_surface")->value;
|
|
if (is_only_one_wall_top && min_width_top_surface > 0) {
|
|
wxString msg_text = _L("This model features text embossment on the top surface. For optimal results, it is "
|
|
"advisable to set the 'One Wall Threshold (min_width_top_surface)' "
|
|
"to 0 for the 'Only One Wall on Top Surfaces' to work best.\n"
|
|
"Yes - Change these settings automatically\n"
|
|
"No - Do not change these settings for me");
|
|
|
|
MessageDialog dialog(wxGetApp().plater(), msg_text, _L("Suggestion"), wxICON_WARNING | wxYES | wxNO);
|
|
if (dialog.ShowModal() == wxID_YES) {
|
|
m_config->set_key_value("min_width_top_surface", new ConfigOptionFloatOrPercent(0, false));
|
|
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_dirty();
|
|
wxGetApp().get_tab(Preset::TYPE_PRINT)->reload_config();
|
|
}
|
|
wxGetApp().plater()->update();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Orca: add submenu for adding handy models
|
|
wxMenu* MenuFactory::append_submenu_add_handy_model(wxMenu* menu, ModelVolumeType type) {
|
|
auto sub_menu = new wxMenu;
|
|
|
|
const std::vector<HandyModel>& models = handy_models();
|
|
for (std::size_t i = 0; i < models.size(); ++i) {
|
|
append_menu_item(sub_menu, wxID_ANY, _(models[i].label), "",
|
|
[i](wxCommandEvent&) { MenuFactory::load_handy_model(i); }, "", menu);
|
|
}
|
|
|
|
return sub_menu;
|
|
}
|
|
|
|
// Create a Text/SVG volume through the matching gizmo. `type == INVALID` means "create a new object".
|
|
// Shared by the add menu and the command palette.
|
|
static void add_volume_with_gizmo(GLGizmosManager::EType gizmo_type, ModelVolumeType type)
|
|
{
|
|
Plater* pl = plater();
|
|
if (!pl)
|
|
return;
|
|
const GLCanvas3D* canvas = pl->canvas3D();
|
|
if (!canvas)
|
|
return;
|
|
GLGizmoBase* gizmo_base = canvas->get_gizmos_manager().get_gizmo(gizmo_type);
|
|
if (!gizmo_base)
|
|
return;
|
|
|
|
ModelVolumeType volume_type = type;
|
|
// no selected object means create new object
|
|
if (volume_type == ModelVolumeType::INVALID)
|
|
volume_type = ModelVolumeType::MODEL_PART;
|
|
|
|
auto screen_position = canvas->get_popup_menu_position();
|
|
if (gizmo_type == GLGizmosManager::Emboss) {
|
|
auto* emboss = dynamic_cast<GLGizmoEmboss*>(gizmo_base);
|
|
if (emboss == nullptr)
|
|
return;
|
|
if (screen_position.has_value())
|
|
emboss->create_volume(volume_type, *screen_position);
|
|
else
|
|
emboss->create_volume(volume_type);
|
|
} else if (gizmo_type == GLGizmosManager::Svg) {
|
|
auto* svg = dynamic_cast<GLGizmoSVG*>(gizmo_base);
|
|
if (svg == nullptr)
|
|
return;
|
|
if (screen_position.has_value())
|
|
svg->create_volume(volume_type, *screen_position);
|
|
else
|
|
svg->create_volume(volume_type);
|
|
}
|
|
}
|
|
|
|
void MenuFactory::add_text_volume(ModelVolumeType type) { add_volume_with_gizmo(GLGizmosManager::Emboss, type); }
|
|
void MenuFactory::add_svg_volume(ModelVolumeType type) { add_volume_with_gizmo(GLGizmosManager::Svg, type); }
|
|
|
|
static void append_menu_itemm_add_(const wxString& name, GLGizmosManager::EType gizmo_type, wxMenu *menu, ModelVolumeType type, bool is_submenu_item) {
|
|
auto add_ = [type, gizmo_type](const wxCommandEvent & /*unnamed*/) { add_volume_with_gizmo(gizmo_type, type); };
|
|
|
|
if (type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER ||
|
|
type == ModelVolumeType::INVALID // cannot use gizmo without selected object
|
|
) {
|
|
wxString item_name = wxString(is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": ") + name;
|
|
menu->AppendSeparator();
|
|
auto def_icon_name = (gizmo_type == GLGizmosManager::Emboss) ? "menu_obj_text" : "menu_obj_svg";
|
|
const std::string icon_name = is_submenu_item ? def_icon_name : ADD_VOLUME_MENU_ITEMS[int(type)].second;
|
|
append_menu_item(menu, wxID_ANY, item_name, "", add_, icon_name, menu);
|
|
}
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item/* = true*/){
|
|
append_menu_itemm_add_(_L("Text"), GLGizmosManager::Emboss, menu, type, is_submenu_item);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item /* = true*/){
|
|
append_menu_itemm_add_(_L("SVG"), GLGizmosManager::Svg, menu, type, is_submenu_item);
|
|
}
|
|
|
|
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 wxString item_name = _(item.first);
|
|
int item_id = menu->FindItem(item_name);
|
|
if (item_id != wxNOT_FOUND)
|
|
menu->Destroy(item_id);
|
|
|
|
item_id = menu->FindItem(item_name + ": " + _L("Text"));
|
|
if (item_id != wxNOT_FOUND)
|
|
menu->Destroy(item_id);
|
|
}
|
|
|
|
for (size_t type = 0; type < ADD_VOLUME_MENU_ITEMS.size(); type++)
|
|
{
|
|
// Skip Precise Seam subtypes except the first one (CENTER) - they are only used for tree icons
|
|
if (type >= int(ModelVolumeType::PRECISE_SEAM_LEFT) &&
|
|
type <= int(ModelVolumeType::PRECISE_SEAM_NEUTRAL))
|
|
continue;
|
|
|
|
auto& item = ADD_VOLUME_MENU_ITEMS[type];
|
|
|
|
// Use special icon for "Add Precise Seam" menu (different from tree icon)
|
|
std::string icon_name = item.second;
|
|
if (type == int(ModelVolumeType::PRECISE_SEAM_CENTER))
|
|
icon_name = "menu_precise_seam_add";
|
|
|
|
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type));
|
|
append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", icon_name,
|
|
[]() { return obj_list()->is_instance_or_object_selected(); }, m_parent);
|
|
}
|
|
|
|
append_menu_item_layers_editing(menu);
|
|
}
|
|
|
|
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(); wxGetApp().params_panel()->switch_to_object(); }, "height_range_modifier", 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);
|
|
}
|
|
|
|
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;
|
|
|
|
// 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
|
|
// BBS remvoe freq setting popupmenu
|
|
// create_freq_settings_popupmenu(menu, is_object_settings, item);
|
|
|
|
//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)
|
|
{
|
|
const wxString menu_name = _L("Change Type");
|
|
|
|
// Delete old menu item if exists
|
|
const int item_id = menu->FindItem(menu_name);
|
|
if (item_id != wxNOT_FOUND)
|
|
menu->Destroy(item_id);
|
|
|
|
// Create submenu
|
|
wxMenu* type_menu = new wxMenu();
|
|
|
|
struct TypeInfo {
|
|
ModelVolumeType type;
|
|
wxString label;
|
|
};
|
|
|
|
std::vector<TypeInfo> types = {
|
|
{ ModelVolumeType::MODEL_PART, _L("Part") },
|
|
{ ModelVolumeType::NEGATIVE_VOLUME, _L("Negative Part") },
|
|
{ ModelVolumeType::PARAMETER_MODIFIER, _L("Modifier") },
|
|
{ ModelVolumeType::SUPPORT_BLOCKER, _L("Support Blocker") },
|
|
{ ModelVolumeType::SUPPORT_ENFORCER, _L("Support Enforcer") },
|
|
// Single "Precise Seam" entry that maps to PRECISE_SEAM_CENTER as the default subtype.
|
|
// set_volume_type() preserves the existing subtype (LEFT/RIGHT/etc.) for volumes that
|
|
// are already Precise Seam; subtype picking is done via the separate
|
|
// "Precise Seam Type" submenu (see append_menu_item_precise_seam_submenu).
|
|
{ ModelVolumeType::PRECISE_SEAM_CENTER, _L("Precise Seam") }
|
|
};
|
|
|
|
for (const auto& info : types) {
|
|
wxMenuItem* item = append_menu_check_item(type_menu, wxID_ANY, info.label, "",
|
|
[type = info.type](wxCommandEvent&) { obj_list()->set_volume_type(type); }, type_menu);
|
|
|
|
// Update checkmark dynamically when menu is shown - check all selected volumes
|
|
m_parent->Bind(wxEVT_UPDATE_UI, [type = info.type](wxUpdateUIEvent& evt) {
|
|
auto model = obj_list()->GetModel();
|
|
auto objs = obj_list()->objects();
|
|
bool has_type = false;
|
|
wxDataViewItemArray sels;
|
|
obj_list()->GetSelections(sels);
|
|
for (auto item : sels) {
|
|
ModelVolumeType vol_type = model->GetVolumeType(item);
|
|
// The "Precise Seam" entry represents all six PS subtypes, so any subtype
|
|
// among selected volumes counts as a match (keeps the checkbox ticked when
|
|
// user has PS_LEFT/RIGHT/etc. selected, not only plain CENTER).
|
|
const bool match = (type == ModelVolumeType::PRECISE_SEAM_CENTER)
|
|
? is_precise_seam(vol_type)
|
|
: (vol_type == type);
|
|
if (match) {
|
|
has_type = true;
|
|
break;
|
|
}
|
|
}
|
|
evt.Check(has_type);
|
|
|
|
// ORCA Fix crash caused by SVG/TEXT volumes cant be Support Enforcer/Blocker type
|
|
// The same applies to Precise Seam subtypes.
|
|
for (auto item : sels) {
|
|
if (model->GetItemType(item) == itVolume){
|
|
auto vol_idx = model->GetVolumeIdByItem(item);
|
|
auto obj_idx = model->GetObjectIdByItem(item);
|
|
if (vol_idx < 0 || obj_idx < 0)
|
|
continue;
|
|
|
|
auto vol = (*objs)[obj_idx]->volumes[vol_idx];
|
|
|
|
// disable Support Enforcer/Blocker if selection contains svg or text
|
|
// (same applies to Precise Seam)
|
|
if (vol != nullptr && (vol->is_svg() || vol->is_text()) && (type == ModelVolumeType::SUPPORT_BLOCKER || type == ModelVolumeType::SUPPORT_ENFORCER || is_precise_seam(type))){
|
|
evt.Enable(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}, item->GetId());
|
|
}
|
|
|
|
menu->Append(wxID_ANY, menu_name, type_menu, _L("Change part type"));
|
|
return nullptr;
|
|
}
|
|
|
|
wxMenuItem* MenuFactory::append_menu_item_instance_to_object(wxMenu* menu)
|
|
{
|
|
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Set as An Individual 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 Individual Objects") : _L("Set as An Individual Object"));
|
|
|
|
evt.Enable(plater()->can_set_instance_to_object());
|
|
}, menu_item->GetId());
|
|
|
|
return menu_item;
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_fill_bed(wxMenu *menu)
|
|
{
|
|
append_menu_item(
|
|
menu, wxID_ANY, _L("Fill bed with copies"), _L("Fill the remaining area of bed with copies of the selected object"),
|
|
[](wxCommandEvent &) { plater()->fill_bed_with_copies(); }, "", nullptr, []() { return plater()->can_increase_instances(); }, m_parent);
|
|
}
|
|
|
|
wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu)
|
|
{
|
|
// BBS: to be checked
|
|
wxMenuItem* menu_item_printable = append_menu_check_item(menu, wxID_ANY, _L("Printable") + "\t" + "V", "",
|
|
[](wxCommandEvent&) { obj_list()->toggle_printable_state(); }, menu);
|
|
|
|
m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
|
|
ObjectList* list = obj_list();
|
|
wxDataViewItemArray sels;
|
|
list->GetSelections(sels);
|
|
wxDataViewItem frst_item = sels[0];
|
|
ItemType type = list->GetModel()->GetItemType(frst_item);
|
|
bool check;
|
|
if (type != itInstance && type != itObject)
|
|
check = false;
|
|
else {
|
|
int obj_idx = list->GetModel()->GetObjectIdByItem(frst_item);
|
|
int inst_idx = type == itObject ? 0 : list->GetModel()->GetInstanceIdByItem(frst_item);
|
|
check = list->object(obj_idx)->instances[inst_idx]->printable;
|
|
}
|
|
|
|
evt.Check(check);
|
|
plater()->set_current_canvas_as_dirty();
|
|
|
|
}, menu_item_printable->GetId());
|
|
|
|
return menu_item_printable;
|
|
}
|
|
|
|
wxMenuItem* MenuFactory::append_menu_item_auto_drop(wxMenu* menu)
|
|
{
|
|
wxString menu_text = _L("Auto Drop");
|
|
wxString menu_tooltip = _L("Automatically drops the selected object to the build plate.");
|
|
wxMenuItem* menu_item_auto_drop = append_menu_check_item(
|
|
menu, wxID_ANY, menu_text, menu_tooltip,
|
|
[](wxCommandEvent&) { obj_list()->toggle_auto_drop(); }, menu);
|
|
|
|
m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
|
|
bool check = wxGetApp().plater()->get_selection().get_auto_drop();
|
|
evt.Check(check);
|
|
},
|
|
menu_item_auto_drop->GetId());
|
|
|
|
return menu_item_auto_drop;
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_rename(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_cgal(wxMenu* menu)
|
|
{
|
|
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix Model"), "",
|
|
[](wxCommandEvent&) { obj_list()->fix_through_cgal(); }, "", menu,
|
|
[]() {return plater()->can_fix_through_cgal(); }, plater());
|
|
|
|
return menu_item;
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_export_stl(wxMenu* menu, bool is_mulity_menu)
|
|
{
|
|
append_menu_item(menu, wxID_ANY, _L("Export as one STL") + dots, "",
|
|
[](wxCommandEvent&) { plater()->export_stl(false, true); }, "", nullptr,
|
|
[is_mulity_menu]() {
|
|
const Selection& selection = plater()->canvas3D()->get_selection();
|
|
if (is_mulity_menu)
|
|
return selection.is_multiple_full_instance() || selection.is_multiple_full_object();
|
|
else
|
|
return selection.is_single_full_instance() || selection.is_single_full_object();
|
|
}, m_parent);
|
|
if (!is_mulity_menu)
|
|
return;
|
|
append_menu_item(menu, wxID_ANY, _L("Export as STLs") + dots, "",
|
|
[](wxCommandEvent&) { plater()->export_stl(false, true, true); }, "", nullptr,
|
|
[]() {
|
|
const Selection& selection = plater()->canvas3D()->get_selection();
|
|
return selection.is_multiple_full_instance() || selection.is_multiple_full_object();
|
|
}, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_export_drc(wxMenu* menu, bool is_mulity_menu)
|
|
{
|
|
append_menu_item(menu, wxID_ANY, _L("Export as one DRC") + dots, "",
|
|
[](wxCommandEvent&) { plater()->export_stl(false, true, false, FT_DRC); }, "", nullptr,
|
|
[is_mulity_menu]() {
|
|
const Selection& selection = plater()->canvas3D()->get_selection();
|
|
if (is_mulity_menu)
|
|
return selection.is_multiple_full_instance() || selection.is_multiple_full_object();
|
|
else
|
|
return selection.is_single_full_instance() || selection.is_single_full_object();
|
|
}, m_parent);
|
|
if (!is_mulity_menu)
|
|
return;
|
|
append_menu_item(menu, wxID_ANY, _L("Export as DRCs") + dots, "",
|
|
[](wxCommandEvent&) { plater()->export_stl(false, true, true, FT_DRC); }, "", nullptr,
|
|
[]() {
|
|
const Selection& selection = plater()->canvas3D()->get_selection();
|
|
return selection.is_multiple_full_instance() || selection.is_multiple_full_object();
|
|
}, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_reload_from_disk(wxMenu* menu)
|
|
{
|
|
append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected parts from disk"),
|
|
[](wxCommandEvent&) { plater()->reload_from_disk(); }, "", menu,
|
|
[]() { return plater()->can_reload_from_disk(); }, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_replace_with_stl(wxMenu *menu)
|
|
{
|
|
append_menu_item(menu, wxID_ANY, _L("Replace 3D file") + dots, _L("Replace the selected part with a new 3D file"),
|
|
[](wxCommandEvent &) { plater()->replace_with_stl(); }, "", menu,
|
|
[]() { return plater()->can_replace_with_stl(); }, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_replace_all_with_stl(wxMenu *menu)
|
|
{
|
|
append_menu_item(menu, wxID_ANY, _L("Replace all with 3D files") + dots, _L("Replace all selected parts with 3D files from folder"),
|
|
[](wxCommandEvent &) { plater()->replace_all_with_stl(); }, "", menu,
|
|
[]() { return plater()->can_replace_all_with_stl(); }, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
|
|
{
|
|
// BBS
|
|
const std::vector<wxString> names = { _L("Change filament"), _L("Set filament 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 filaments_cnt = filaments_count();
|
|
if (filaments_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]);
|
|
// BBS: set default extruder to 1
|
|
initial_extruder = config.has("extruder") ? config.extruder() : 1;
|
|
}
|
|
|
|
for (int i = 0; i <= filaments_cnt; i++)
|
|
{
|
|
bool is_active_extruder = i == initial_extruder;
|
|
int icon_idx = i == 0 ? 0 : i - 1;
|
|
|
|
wxString item_name = _L("Default");
|
|
|
|
if (i > 0) {
|
|
auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i - 1]);
|
|
if (preset == nullptr) {
|
|
item_name = wxString::Format(_L("Filament %d"), i);
|
|
} else {
|
|
item_name = from_u8(preset->label(false));
|
|
}
|
|
}
|
|
|
|
if (is_active_extruder) {
|
|
item_name << " (" + _L("current") + ")";
|
|
}
|
|
|
|
if (icon_idx >= 0 && icon_idx < icons.size()) {
|
|
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);
|
|
} else {
|
|
append_menu_item(
|
|
extruder_selection_menu, wxID_ANY, item_name, "", [i](wxCommandEvent &) { obj_list()->set_extruder_for_selected_items(i); }, "", 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 build volume"), _L("Scale an object to fit the build volume"),
|
|
[](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu);
|
|
}
|
|
|
|
void MenuFactory::append_menu_items_flush_options(wxMenu* menu)
|
|
{
|
|
const wxString name = _L("Flush Options");
|
|
// Delete old menu item
|
|
const int item_id = menu->FindItem(name);
|
|
if (item_id != wxNOT_FOUND)
|
|
menu->Destroy(item_id);
|
|
|
|
bool show_flush_option_menu = false;
|
|
ObjectList* object_list = obj_list();
|
|
const Selection& selection = get_selection();
|
|
if (selection.get_object_idx() < 0)
|
|
return;
|
|
if (wxGetApp().plater()->get_partplate_list().get_curr_plate()->contains(selection.get_bounding_box())) {
|
|
auto plate_extruders = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_extruders();
|
|
for (auto extruder : plate_extruders) {
|
|
if (extruder != plate_extruders[0])
|
|
show_flush_option_menu = true;
|
|
}
|
|
}
|
|
if (!show_flush_option_menu)
|
|
return;
|
|
|
|
DynamicPrintConfig& global_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
|
ModelConfig& select_object_config = object_list->object(selection.get_object_idx())->config;
|
|
|
|
wxMenu* flush_options_menu = new wxMenu();
|
|
auto can_flush = [&global_config]() {
|
|
auto option = global_config.option("enable_prime_tower");
|
|
return option ? option->getBool() : false;
|
|
};
|
|
append_menu_check_item(flush_options_menu, wxID_ANY, _L("Flush into objects' infill"), "",
|
|
[&select_object_config, &global_config](wxCommandEvent&) {
|
|
const ConfigOption* option = select_object_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][0]);
|
|
if (!option) {
|
|
option = global_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][0]);
|
|
}
|
|
select_object_config.set_key_value(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][0], new ConfigOptionBool(!option->getBool()));
|
|
wxGetApp().obj_settings()->UpdateAndShow(true);
|
|
}, menu, can_flush,
|
|
[&select_object_config, &global_config]() {
|
|
const ConfigOption* option = select_object_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][0]);
|
|
if (!option) {
|
|
option = global_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][0]);
|
|
}
|
|
return option->getBool();
|
|
}, m_parent);
|
|
|
|
append_menu_check_item(flush_options_menu, wxID_ANY, _L("Flush into this object"), "",
|
|
[&select_object_config, &global_config](wxCommandEvent&) {
|
|
const ConfigOption* option = select_object_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][1]);
|
|
if (!option) {
|
|
option = global_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][1]);
|
|
}
|
|
select_object_config.set_key_value(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][1], new ConfigOptionBool(!option->getBool()));
|
|
wxGetApp().obj_settings()->UpdateAndShow(true);
|
|
}, menu, can_flush,
|
|
[&select_object_config, &global_config]() {
|
|
const ConfigOption* option = select_object_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][1]);
|
|
if (!option) {
|
|
option = global_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][1]);
|
|
}
|
|
return option->getBool();
|
|
}, m_parent);
|
|
|
|
append_menu_check_item(flush_options_menu, wxID_ANY, _L("Flush into objects' support"), "",
|
|
[&select_object_config, &global_config](wxCommandEvent&) {
|
|
const ConfigOption* option = select_object_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][2]);
|
|
if (!option) {
|
|
option = global_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][2]);
|
|
}
|
|
select_object_config.set_key_value(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][2], new ConfigOptionBool(!option->getBool()));
|
|
wxGetApp().obj_settings()->UpdateAndShow(true);
|
|
}, menu, can_flush,
|
|
[&select_object_config, &global_config]() {
|
|
const ConfigOption* option = select_object_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][2]);
|
|
if (!option) {
|
|
option = global_config.option(FREQ_SETTINGS_BUNDLE_FFF["Flush options"][2]);
|
|
}
|
|
return option->getBool();
|
|
}, m_parent);
|
|
|
|
size_t i = 0;
|
|
for (auto node = menu->GetMenuItems().GetFirst(); node; node = node->GetNext())
|
|
{
|
|
i++;
|
|
wxMenuItem* item = node->GetData();
|
|
if (item->GetItemLabelText() == _L("Edit in Parameter Table"))
|
|
break;
|
|
}
|
|
menu->Insert(i, wxID_ANY, _L("Flush Options"), flush_options_menu);
|
|
}
|
|
|
|
void MenuFactory::append_menu_items_convert_unit(wxMenu* menu)
|
|
{
|
|
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 Inches") },
|
|
{ConversionType::CONV_TO_INCH , _L("Restore to Inch") },
|
|
{ConversionType::CONV_FROM_METER, _L("Convert from Meters") },
|
|
{ConversionType::CONV_TO_METER , _L("Restore to Meter") } };
|
|
|
|
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);
|
|
}
|
|
else if (menu_id != wxNOT_FOUND) {
|
|
// Delete menu item
|
|
menu->Destroy(menu_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_merge_to_multipart_object(wxMenu* menu)
|
|
{
|
|
append_menu_item(menu, wxID_ANY, _L("Assemble"), _L("Assemble the selected objects into an object with multiple parts"),
|
|
[](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("Assemble"), _L("Assemble the selected objects into an object with single part"),
|
|
[](wxCommandEvent&) { obj_list()->merge(false); }, "", menu,
|
|
[]() { return obj_list()->can_merge_to_single_object(); }, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_merge_parts_to_single_part(wxMenu* menu)
|
|
{
|
|
menu->AppendSeparator();
|
|
append_menu_item(menu, wxID_ANY, _L("Mesh boolean"), _L("Mesh boolean operations including union and subtraction"),
|
|
[](wxCommandEvent&) { obj_list()->boolean/*merge_volumes*/(); }, "", menu,
|
|
[]() { return obj_list()->can_mesh_boolean(); }, 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 along the X Axis"),
|
|
[](wxCommandEvent&) { plater()->mirror(X); }, "menu_mirror_x", menu);
|
|
append_menu_item(mirror_menu, wxID_ANY, _L("Along Y Axis"), _L("Mirror along the Y Axis"),
|
|
[](wxCommandEvent&) { plater()->mirror(Y); }, "menu_mirror_y", menu);
|
|
append_menu_item(mirror_menu, wxID_ANY, _L("Along Z Axis"), _L("Mirror along the Z Axis"),
|
|
[](wxCommandEvent&) { plater()->mirror(Z); }, "menu_mirror_z", menu);
|
|
|
|
append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror object"), "",
|
|
[]() { return plater()->can_mirror(); }, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_precise_seam_submenu(wxMenu* menu)
|
|
{
|
|
wxString submenu_name = _L("Precise Seam Type");
|
|
|
|
// Remove existing submenu if present (menu is rebuilt on every right-click)
|
|
const int menu_item_id = menu->FindItem(submenu_name);
|
|
if (menu_item_id != wxNOT_FOUND)
|
|
menu->Destroy(menu_item_id);
|
|
|
|
// --- Precondition: ALL selected volumes must be Precise Seam ---
|
|
// Mixed selections (PS + non-PS) are ambiguous for subtype switching: applying a subtype
|
|
// would implicitly convert the non-PS volumes to PS, which is not what the user expects
|
|
// from a subtype picker. For mixed selections the user should first use
|
|
// "Change type → Precise Seam" to unify them, then come back to this submenu.
|
|
wxDataViewItemArray sels;
|
|
obj_list()->GetSelections(sels);
|
|
if (sels.IsEmpty())
|
|
return;
|
|
|
|
// A right-click on a volume in the 3D view routes through Plater::show_context_menu
|
|
// → ObjectList::update_selections(), which intentionally preserves a pre-existing
|
|
// settings-row selection alongside the clicked volume. As a result GetSelections()
|
|
// may return settings-row items that are children of a volume. Treat each settings
|
|
// row as selecting its parent volume (same pattern used by set_volume_type()
|
|
// internally). Without this resolution, GetVolumeType(settings_row) returns INVALID
|
|
// and the submenu would hide even when a valid PS volume is in the selection.
|
|
//
|
|
// Precomputing the resolved PS types once here also lets the checkmark loop below
|
|
// display checks correctly for selections that contain settings rows.
|
|
std::vector<ModelVolumeType> selected_ps_types;
|
|
selected_ps_types.reserve(sels.size());
|
|
|
|
auto* model = obj_list()->GetModel();
|
|
for (const auto& sel_item : sels) {
|
|
wxDataViewItem vol_item = sel_item;
|
|
const ItemType type_mask = model->GetItemType(sel_item);
|
|
if (!(type_mask & itVolume)) {
|
|
// Only settings rows whose parent is a volume map to "selecting that volume".
|
|
// Any other non-volume item (object, instance, etc.) means the selection is
|
|
// not purely PS-volume-based — hide the submenu.
|
|
if ((type_mask & itSettings) && (model->GetItemType(model->GetParent(sel_item)) & itVolume))
|
|
vol_item = model->GetParent(sel_item);
|
|
else
|
|
return;
|
|
}
|
|
const ModelVolumeType vol_type = model->GetVolumeType(vol_item);
|
|
if (!is_precise_seam(vol_type))
|
|
return;
|
|
selected_ps_types.push_back(vol_type);
|
|
}
|
|
|
|
// Match the label used by append_menu_item_change_type, including its translation.
|
|
int insert_pos = wxNOT_FOUND;
|
|
int change_type_id = menu->FindItem(_L("Change Type"));
|
|
if (change_type_id != wxNOT_FOUND) {
|
|
for (size_t i = 0; i < menu->GetMenuItemCount(); i++) {
|
|
wxMenuItem* item = menu->FindItemByPosition(i);
|
|
if (item && item->GetId() == change_type_id) {
|
|
insert_pos = i + 1; // insert directly after the Change Type entry
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Build the subtype submenu ---
|
|
wxMenu* ps_menu = new wxMenu();
|
|
|
|
// Array of all 6 Precise Seam subtypes with labels.
|
|
// "Seam ..." prefix disambiguates from other i18n contexts (extruder Left/Right, "Center on bed", etc.)
|
|
static const std::array<std::pair<const char*, ModelVolumeType>, 6> PS_TYPES = {{
|
|
{L("Seam Center"), ModelVolumeType::PRECISE_SEAM_CENTER},
|
|
{L("Seam Left"), ModelVolumeType::PRECISE_SEAM_LEFT},
|
|
{L("Seam Right"), ModelVolumeType::PRECISE_SEAM_RIGHT},
|
|
{L("Seam Enforced"), ModelVolumeType::PRECISE_SEAM_ENFORCED},
|
|
{L("Seam Blocked"), ModelVolumeType::PRECISE_SEAM_BLOCKED},
|
|
{L("Seam Neutral"), ModelVolumeType::PRECISE_SEAM_NEUTRAL},
|
|
}};
|
|
|
|
for (const auto& ps_type : PS_TYPES) {
|
|
wxString label = _(ps_type.first);
|
|
|
|
// Check-items (not radio): radio groups in wxWidgets auto-select the first item when
|
|
// no item is explicitly checked, which misleads the user into seeing "Seam Center"
|
|
// as the active subtype for mixed-subtype selections. With check-items we can leave
|
|
// every item unchecked in that case, and the menu matches the pattern of the
|
|
// neighbouring "Change type" submenu (see append_menu_item_change_type).
|
|
//
|
|
// Handler: delegate to set_volume_type() with preserve_ps_subtype=false. The user
|
|
// explicitly picked a subtype here, so "Seam Center" must set every selected PS
|
|
// volume to CENTER verbatim — preservation (the default for "Change type") would
|
|
// keep existing subtypes and make CENTER unreachable on mixed-subtype selections.
|
|
// set_volume_type() already handles multi-select iteration, the last-solid-part
|
|
// guard, and the PS group-change reordering via move_volume_to_end().
|
|
wxMenuItem* item = append_menu_check_item(ps_menu, wxID_ANY, label, "",
|
|
[ps_type](wxCommandEvent&) {
|
|
obj_list()->set_volume_type(ps_type.second, /*preserve_ps_subtype=*/false);
|
|
},
|
|
ps_menu);
|
|
|
|
// Tick every subtype that is present in the current selection. Same pattern as the
|
|
// neighbouring "Change type" submenu (where selecting [Part + Modifier] ticks both
|
|
// "Part" and "Modifier" boxes). For homogeneous selections exactly one checkbox is
|
|
// ticked; for mixed subtype selections (e.g. PS_LEFT + PS_RIGHT) both "Seam Left"
|
|
// and "Seam Right" appear checked, so the user sees at a glance which subtypes are
|
|
// currently in the selection.
|
|
//
|
|
// Uses the precomputed selected_ps_types (which resolved settings rows to their
|
|
// parent volumes); iterating sels directly would miss subtypes for selections that
|
|
// arrive via settings rows from right-click-in-3D-view.
|
|
const bool is_present = std::find(selected_ps_types.begin(), selected_ps_types.end(),
|
|
ps_type.second) != selected_ps_types.end();
|
|
if (is_present && item)
|
|
item->Check(true);
|
|
}
|
|
|
|
// Add submenu to parent menu
|
|
append_submenu(menu, ps_menu, wxID_ANY,
|
|
submenu_name,
|
|
_L("Choose precise seam subtype"),
|
|
"menu_precise_seam_type",
|
|
[]() { return true; },
|
|
m_parent,
|
|
insert_pos);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
|
|
{
|
|
wxString name = _L("Edit text");
|
|
|
|
auto can_edit_text = []() {
|
|
if (plater() == nullptr)
|
|
return false;
|
|
const Selection& selection = plater()->get_selection();
|
|
if (selection.volumes_count() != 1)
|
|
return false;
|
|
const GLVolume* gl_volume = selection.get_first_volume();
|
|
if (gl_volume == nullptr)
|
|
return false;
|
|
const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects);
|
|
if (volume == nullptr)
|
|
return false;
|
|
return volume->is_text();
|
|
};
|
|
|
|
if (menu != &m_text_part_menu) {
|
|
const int menu_item_id = menu->FindItem(name);
|
|
if (menu_item_id != wxNOT_FOUND)
|
|
menu->Destroy(menu_item_id);
|
|
if (!can_edit_text())
|
|
return;
|
|
}
|
|
|
|
wxString description = _L("Ability to change text, font, size, ...");
|
|
std::string icon = "cog";
|
|
auto open_emboss = [](const wxCommandEvent &) {
|
|
GLGizmosManager &mng = plater()->get_view3D_canvas3D()->get_gizmos_manager();
|
|
if (mng.get_current_type() == GLGizmosManager::Emboss)
|
|
mng.open_gizmo(GLGizmosManager::Emboss); // close() and reopen - move to be visible
|
|
mng.open_gizmo(GLGizmosManager::Emboss);
|
|
};
|
|
append_menu_item(menu, wxID_ANY, name, description, open_emboss, icon, nullptr, can_edit_text, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_edit_svg(wxMenu *menu)
|
|
{
|
|
wxString name = _L("Edit SVG");
|
|
auto can_edit_svg = []() {
|
|
if (plater() == nullptr)
|
|
return false;
|
|
const Selection& selection = plater()->get_selection();
|
|
if (selection.volumes_count() != 1)
|
|
return false;
|
|
const GLVolume* gl_volume = selection.get_first_volume();
|
|
if (gl_volume == nullptr)
|
|
return false;
|
|
const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects);
|
|
if (volume == nullptr)
|
|
return false;
|
|
return volume->is_svg();
|
|
};
|
|
|
|
if (menu != &m_svg_part_menu) {
|
|
const int menu_item_id = menu->FindItem(name);
|
|
if (menu_item_id != wxNOT_FOUND)
|
|
menu->Destroy(menu_item_id);
|
|
if (!can_edit_svg())
|
|
return;
|
|
}
|
|
|
|
wxString description = _L("Change SVG source file, projection, size, ...");
|
|
std::string icon = "cog";
|
|
auto open_svg = [](const wxCommandEvent &) {
|
|
GLGizmosManager &mng = plater()->get_view3D_canvas3D()->get_gizmos_manager();
|
|
if (mng.get_current_type() == GLGizmosManager::Svg)
|
|
mng.open_gizmo(GLGizmosManager::Svg); // close() and reopen - move to be visible
|
|
mng.open_gizmo(GLGizmosManager::Svg);
|
|
};
|
|
append_menu_item(menu, wxID_ANY, name, description, open_svg, icon, nullptr, can_edit_svg, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_invalidate_cut_info(wxMenu *menu)
|
|
{
|
|
const wxString menu_name = _L("Invalidate cut info");
|
|
|
|
auto menu_item_id = menu->FindItem(menu_name);
|
|
if (menu_item_id != wxNOT_FOUND)
|
|
// Delete old menu item if selected object isn't cut
|
|
menu->Destroy(menu_item_id);
|
|
|
|
if (obj_list()->has_selected_cut_object())
|
|
append_menu_item(menu, wxID_ANY, menu_name, "", [](wxCommandEvent &) { obj_list()->invalidate_cut_info_for_selection(); },
|
|
"", menu, []() { return true; }, 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_primitives = append_submenu_add_generic(&m_default_menu, ModelVolumeType::INVALID);
|
|
wxMenu* sub_menu_handy = append_submenu_add_handy_model(&m_default_menu, ModelVolumeType::INVALID);
|
|
|
|
append_submenu(&m_default_menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "menu_add_part",
|
|
[]() {return true; }, m_parent);
|
|
append_submenu(&m_default_menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "menu_add_part",
|
|
[]() {return true; }, m_parent);
|
|
append_menu_item(&m_default_menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models
|
|
[](wxCommandEvent&) { plater()->add_file(); }, "menu_add_part", &m_default_menu,
|
|
[]() {return wxGetApp().plater()->can_add_model(); }, m_parent);
|
|
|
|
m_default_menu.AppendSeparator();
|
|
|
|
append_menu_check_item(&m_default_menu, wxID_ANY, _L("Show Labels"), "",
|
|
[](wxCommandEvent&) { plater()->show_view3D_labels(!plater()->are_view3D_labels_shown()); plater()->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, &m_default_menu,
|
|
[]() { return plater()->is_view3D_shown(); }, []() { return plater()->are_view3D_labels_shown(); }, m_parent);
|
|
}
|
|
|
|
void MenuFactory::create_common_object_menu(wxMenu* menu)
|
|
{
|
|
append_menu_item_rename(menu);
|
|
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);
|
|
menu->AppendSeparator();
|
|
|
|
append_menu_item_reload_from_disk(menu);
|
|
append_menu_item_export_stl(menu);
|
|
append_menu_item_export_drc(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_cgal(menu);
|
|
append_menu_items_mirror(menu);
|
|
}
|
|
|
|
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 multiple objects"),
|
|
[](wxCommandEvent&) { plater()->split_object(); }, "menu_split_objects", &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 multiple parts"),
|
|
[](wxCommandEvent&) { plater()->split_volume(); }, "menu_split_parts", &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) || plater()->can_split(false); }, m_parent);
|
|
m_object_menu.AppendSeparator();
|
|
|
|
// BBS: remove Layers Editing
|
|
m_object_menu.AppendSeparator();
|
|
|
|
// "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
|
|
}
|
|
|
|
void MenuFactory::create_extra_object_menu()
|
|
{
|
|
// Object instances / conversions
|
|
append_menu_items_instance_manipulation(&m_object_menu);
|
|
m_object_menu.AppendSeparator();
|
|
append_menu_item_instance_to_object(&m_object_menu);
|
|
m_object_menu.AppendSeparator();
|
|
//append_menu_item_fill_bed(&m_object_menu);
|
|
// Object Clone
|
|
append_menu_item_clone(&m_object_menu);
|
|
// Object Repair
|
|
append_menu_item_fix_through_cgal(&m_object_menu);
|
|
// Object Simplify
|
|
append_menu_item_simplify(&m_object_menu);
|
|
// Object Mesh Subdivision
|
|
append_menu_item_smooth_mesh(&m_object_menu);
|
|
// merge to single part
|
|
append_menu_item_merge_parts_to_single_part(&m_object_menu);
|
|
// Object Center
|
|
append_menu_item_center(&m_object_menu);
|
|
// Object Drop
|
|
append_menu_item_drop(&m_object_menu);
|
|
// Object Split
|
|
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 multiple objects"),
|
|
[](wxCommandEvent&) { plater()->split_object(); }, "menu_split_objects", &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 multiple parts"),
|
|
[](wxCommandEvent&) { plater()->split_volume(); }, "menu_split_parts", &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); }, m_parent);
|
|
|
|
// Mirror
|
|
append_menu_items_mirror(&m_object_menu);
|
|
// Delete
|
|
append_menu_item_delete(&m_object_menu);
|
|
m_object_menu.AppendSeparator();
|
|
// Modifier Part
|
|
// BBS
|
|
append_menu_items_add_volume(&m_object_menu);
|
|
m_object_menu.AppendSeparator();
|
|
// Set filament insert menu item here
|
|
// Set Printable
|
|
wxMenuItem* menu_item_printable = append_menu_item_printable(&m_object_menu);
|
|
m_object_menu.AppendSeparator();
|
|
wxMenuItem* menu_item_auto_drop = append_menu_item_auto_drop(&m_object_menu);
|
|
m_object_menu.AppendSeparator();
|
|
|
|
append_menu_item_per_object_process(&m_object_menu);
|
|
// Enter per object parameters
|
|
append_menu_item_per_object_settings(&m_object_menu);
|
|
m_object_menu.AppendSeparator();
|
|
append_menu_item_reload_from_disk(&m_object_menu);
|
|
append_menu_item_replace_with_stl(&m_object_menu);
|
|
append_menu_item_replace_all_with_stl(&m_object_menu);
|
|
append_menu_item_export_stl(&m_object_menu);
|
|
append_menu_item_export_drc(&m_object_menu);
|
|
}
|
|
|
|
void MenuFactory::create_bbl_assemble_object_menu()
|
|
{
|
|
// Delete
|
|
append_menu_item_delete(&m_assemble_object_menu);
|
|
// Object Repair
|
|
append_menu_item_fix_through_cgal(&m_assemble_object_menu);
|
|
// Object Simplify
|
|
append_menu_item_simplify(&m_assemble_object_menu);
|
|
// Object Mesh Subdivision
|
|
append_menu_item_smooth_mesh(&m_assemble_object_menu);
|
|
m_assemble_object_menu.AppendSeparator();
|
|
}
|
|
|
|
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 multiple objects"),
|
|
[](wxCommandEvent&) { plater()->split_object(); }, "", 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("Auto orientation"), _L("Auto orient the object to improve print quality"),
|
|
[](wxCommandEvent&) { plater()->optimize_rotation(); });
|
|
}
|
|
|
|
void MenuFactory::create_part_menu()
|
|
{
|
|
wxMenu* menu = &m_part_menu;
|
|
append_menu_item_rename(menu);
|
|
append_menu_item_delete(menu);
|
|
append_menu_item_reload_from_disk(menu);
|
|
append_menu_item_export_stl(menu);
|
|
append_menu_item_export_drc(menu);
|
|
append_menu_item_fix_through_cgal(menu);
|
|
append_menu_items_mirror(menu);
|
|
append_menu_item_merge_parts_to_single_part(menu);
|
|
|
|
append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into multiple parts"),
|
|
[](wxCommandEvent&) { plater()->split_volume(); }, "split_parts", nullptr,
|
|
[]() { return plater()->can_split(false); }, m_parent);
|
|
|
|
menu->AppendSeparator();
|
|
append_menu_item_change_type(menu);
|
|
append_menu_item_precise_seam_submenu(menu);
|
|
append_menu_items_mirror(&m_part_menu);
|
|
append_menu_item(&m_part_menu, wxID_ANY, _L("Split"), _L("Split the selected object into multiple parts"),
|
|
[](wxCommandEvent&) { plater()->split_volume(); }, "split_parts", nullptr,
|
|
[]() { return plater()->can_split(false); }, m_parent);
|
|
m_part_menu.AppendSeparator();
|
|
append_menu_item_per_object_process(&m_part_menu);
|
|
append_menu_item_per_object_settings(&m_part_menu);
|
|
}
|
|
|
|
void MenuFactory::create_text_part_menu()
|
|
{
|
|
wxMenu* menu = &m_text_part_menu;
|
|
|
|
append_menu_item_edit_text(menu);
|
|
append_menu_item_delete(menu);
|
|
append_menu_item_fix_through_cgal(menu);
|
|
append_menu_item_simplify(menu);
|
|
append_menu_item_center(menu);
|
|
append_menu_items_mirror(menu);
|
|
menu->AppendSeparator();
|
|
append_menu_item_per_object_process(menu);
|
|
append_menu_item_per_object_settings(menu);
|
|
append_menu_item_change_type(menu);
|
|
}
|
|
|
|
void MenuFactory::create_svg_part_menu()
|
|
{
|
|
wxMenu* menu = &m_svg_part_menu;
|
|
|
|
append_menu_item_edit_svg(menu);
|
|
append_menu_item_delete(menu);
|
|
append_menu_item_fix_through_cgal(menu);
|
|
append_menu_item_simplify(menu);
|
|
append_menu_items_mirror(menu);
|
|
menu->AppendSeparator();
|
|
append_menu_item_per_object_process(menu);
|
|
append_menu_item_per_object_settings(menu);
|
|
append_menu_item_change_type(menu);
|
|
}
|
|
|
|
void MenuFactory::create_bbl_part_menu()
|
|
{
|
|
wxMenu* menu = &m_part_menu;
|
|
|
|
append_menu_item_delete(menu);
|
|
append_menu_item_edit_text(menu);
|
|
append_menu_item_fix_through_cgal(menu);
|
|
append_menu_item_simplify(menu);
|
|
append_menu_item_smooth_mesh(menu);
|
|
append_menu_item_center(menu);
|
|
append_menu_item_drop(menu);
|
|
append_menu_items_mirror(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 multiple objects"),
|
|
[](wxCommandEvent&) { plater()->split_object(); }, "menu_split_objects", menu,
|
|
[]() { return plater()->can_split(true); }, m_parent);
|
|
append_menu_item(split_menu, wxID_ANY, _L("To Parts"), _L("Split the selected object into multiple parts"),
|
|
[](wxCommandEvent&) { plater()->split_volume(); }, "menu_split_parts", menu,
|
|
[]() { return plater()->can_split(false); }, m_parent);
|
|
|
|
append_submenu(menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "",
|
|
[]() { return plater()->can_split(true); }, m_parent);
|
|
menu->AppendSeparator();
|
|
append_menu_item_per_object_process(menu);
|
|
append_menu_item_per_object_settings(menu);
|
|
append_menu_item_change_type(menu);
|
|
append_menu_item_reload_from_disk(menu);
|
|
append_menu_item_replace_with_stl(menu);
|
|
append_menu_item_replace_all_with_stl(menu);
|
|
}
|
|
|
|
void MenuFactory::create_bbl_assemble_part_menu()
|
|
{
|
|
wxMenu* menu = &m_assemble_part_menu;
|
|
|
|
append_menu_item_delete(menu);
|
|
append_menu_item_simplify(menu);
|
|
append_menu_item_smooth_mesh(menu);
|
|
menu->AppendSeparator();
|
|
}
|
|
|
|
void MenuFactory::create_filament_action_menu(bool init, int active_filament_menu_id)
|
|
{
|
|
wxMenu *menu = &m_filament_action_menu;
|
|
|
|
// ORCA rebuild menu everytime instead checking existing of every item then deleting
|
|
while (menu->GetMenuItemCount() > 0)
|
|
menu->Destroy(menu->FindItemByPosition(0));
|
|
|
|
//if (init) { //
|
|
append_menu_item(
|
|
menu, wxID_ANY, _L("Edit"), "", [](wxCommandEvent&) {
|
|
plater()->sidebar().edit_filament(); }, "", nullptr,
|
|
[]() { return true; }, m_parent);
|
|
//}
|
|
|
|
wxMenu* sub_menu = new wxMenu();
|
|
std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
|
|
int filaments_cnt = Sidebar::should_show_SEMM_buttons() ? icons.size() : 0;
|
|
for (int i = 0; i < filaments_cnt; i++) {
|
|
if (i == active_filament_menu_id)
|
|
continue;
|
|
|
|
auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i]);
|
|
wxString item_name = preset ? from_u8(preset->label(false)) : wxString::Format(_L("Filament %d"), i + 1);
|
|
|
|
append_menu_item(sub_menu, wxID_ANY, item_name, "",
|
|
[i](wxCommandEvent&) { plater()->sidebar().change_filament(-2, i); }, *icons[i], menu,
|
|
[]() { return true; }, m_parent);
|
|
}
|
|
append_submenu(menu, sub_menu, wxID_ANY, _L("Merge with"), "", "",
|
|
[filaments_cnt]() { return filaments_cnt > 1; }, m_parent);
|
|
|
|
// Decompose a target colour into a printable mix of the loaded filaments. Placed before the
|
|
append_menu_item(
|
|
menu, wxID_ANY, _L("Decompose Color"), "", [](wxCommandEvent&) {
|
|
plater()->sidebar().decompose_filament_color(kSidebarContextMenuFilamentId); }, "", nullptr,
|
|
[]() { return plater()->sidebar().combos_filament().size() >= 2; }, m_parent);
|
|
|
|
menu->AppendSeparator(); // ORCA use seperator for reducing accidental clicks to delete
|
|
|
|
// ORCA use delete item on end of menu to prevent accidental clicks. clicking to submenus(merge) already not allowed by OS
|
|
append_menu_item(
|
|
menu, wxID_ANY, _L("Delete"), _L("Delete this filament"), [](wxCommandEvent&) {
|
|
plater()->sidebar().delete_filament(-2); }, "", nullptr,
|
|
[]() {
|
|
return plater()->sidebar().combos_filament().size() > 1
|
|
// Orca: only show delete filament option for SEMM machines unless is BBL
|
|
&& Sidebar::should_show_SEMM_buttons();
|
|
}, m_parent);
|
|
}
|
|
|
|
//BBS: add part plate related logic
|
|
void MenuFactory::create_plate_menu()
|
|
{
|
|
wxMenu* menu = &m_plate_menu;
|
|
// select objects on current plate
|
|
append_menu_item(menu, wxID_ANY, _L("Select All"), _L("Select all objects on the current plate"),
|
|
[](wxCommandEvent&) {
|
|
plater()->select_curr_plate_all();
|
|
}, "", nullptr, []() {
|
|
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
return !plate->get_objects().empty();
|
|
}, m_parent);
|
|
|
|
// select objects on all plates
|
|
append_menu_item(menu, wxID_ANY, _L("Select All Plates"), _L("Select all objects on all plates"),
|
|
[](wxCommandEvent&) {
|
|
plater()->select_all();
|
|
}, "", nullptr, []() {
|
|
return !plater()->model().objects.empty();
|
|
}, m_parent);
|
|
|
|
// delete objects on current plate
|
|
append_menu_item(menu, wxID_ANY, _L("Delete All"), _L("Delete all objects on the current plate"),
|
|
[](wxCommandEvent&) {
|
|
plater()->remove_curr_plate_all();
|
|
}, "", nullptr, []() {
|
|
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
return !plate->get_objects().empty();
|
|
}, m_parent);
|
|
|
|
// arrange objects on current plate
|
|
append_menu_item(menu, wxID_ANY, _L("Arrange"), _L("Arrange current plate"),
|
|
[](wxCommandEvent&) {
|
|
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
plater()->set_prepare_state(Job::PREPARE_STATE_MENU);
|
|
plater()->arrange();
|
|
}, "", nullptr,
|
|
[]() {
|
|
return !plater()->get_partplate_list().get_selected_plate()->get_objects().empty();
|
|
},
|
|
m_parent);
|
|
|
|
// reload all objects on current plate
|
|
append_menu_item(
|
|
menu, wxID_ANY, _L("Reload All"), _L("Reload all from disk"),
|
|
[](wxCommandEvent&) {
|
|
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
plater()->set_prepare_state(Job::PREPARE_STATE_MENU);
|
|
plater()->reload_all_from_disk();
|
|
},
|
|
"", nullptr, []() { return !plater()->get_partplate_list().get_selected_plate()->get_objects().empty(); }, m_parent);
|
|
|
|
// orient objects on current plate
|
|
append_menu_item(menu, wxID_ANY, _L("Auto Rotate"), _L("Auto rotate current plate"),
|
|
[](wxCommandEvent&) {
|
|
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
//BBS TODO call auto rotate for current plate
|
|
plater()->set_prepare_state(Job::PREPARE_STATE_MENU);
|
|
plater()->orient();
|
|
}, "", nullptr,
|
|
[]() {
|
|
return !plater()->get_partplate_list().get_selected_plate()->get_objects().empty();
|
|
}, m_parent);
|
|
|
|
// delete current plate
|
|
#ifdef __WINDOWS__
|
|
append_menu_item(menu, wxID_ANY, _L("Delete Plate"), _L("Remove the selected plate"),
|
|
[](wxCommandEvent&) { plater()->delete_plate(); }, "menu_delete", nullptr,
|
|
[]() { return plater()->can_delete_plate(); }, m_parent);
|
|
#else
|
|
append_menu_item(menu, wxID_ANY, _L("Delete Plate"), _L("Remove the selected plate"),
|
|
[](wxCommandEvent&) { plater()->delete_plate(); }, "", nullptr,
|
|
[]() { return plater()->can_delete_plate(); }, m_parent);
|
|
#endif
|
|
|
|
|
|
// add shapes
|
|
menu->AppendSeparator();
|
|
wxMenu* sub_menu_primitives = append_submenu_add_generic(menu, ModelVolumeType::INVALID);
|
|
wxMenu* sub_menu_handy = append_submenu_add_handy_model(menu, ModelVolumeType::INVALID);
|
|
|
|
append_submenu(menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "menu_add_part",
|
|
[]() {return true; }, m_parent);
|
|
append_submenu(menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "menu_add_part",
|
|
[]() {return true; }, m_parent);
|
|
append_menu_item(menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models
|
|
[](wxCommandEvent&) { plater()->add_file(); }, "menu_add_part", menu,
|
|
[]() {return wxGetApp().plater()->can_add_model(); }, m_parent);
|
|
|
|
append_menu_item_replace_all_with_stl(menu);
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
void MenuFactory::init(wxWindow* parent)
|
|
{
|
|
m_parent = parent;
|
|
|
|
create_default_menu();
|
|
//BBS
|
|
//create_object_menu();
|
|
create_sla_object_menu();
|
|
//create_part_menu();
|
|
create_text_part_menu();
|
|
create_svg_part_menu();
|
|
create_extra_object_menu();
|
|
create_bbl_part_menu();
|
|
create_bbl_assemble_object_menu();
|
|
create_bbl_assemble_part_menu();
|
|
|
|
//BBS: add part plate related logic
|
|
create_plate_menu();
|
|
|
|
create_filament_action_menu(true, -1);
|
|
|
|
// create "Instance to Object" menu item
|
|
append_menu_item_instance_to_object(&m_instance_menu);
|
|
}
|
|
|
|
void MenuFactory::update()
|
|
{
|
|
update_default_menu();
|
|
update_object_menu();
|
|
}
|
|
|
|
wxMenu* MenuFactory::default_menu()
|
|
{
|
|
return &m_default_menu;
|
|
}
|
|
|
|
wxMenu* MenuFactory::object_menu()
|
|
{
|
|
append_menu_items_convert_unit(&m_object_menu);
|
|
append_menu_items_flush_options(&m_object_menu);
|
|
append_menu_item_invalidate_cut_info(&m_object_menu);
|
|
append_menu_item_edit_text(&m_object_menu);
|
|
append_menu_item_edit_svg(&m_object_menu);
|
|
append_menu_item_change_filament(&m_object_menu);
|
|
return &m_object_menu;
|
|
}
|
|
|
|
wxMenu* MenuFactory::sla_object_menu()
|
|
{
|
|
append_menu_items_convert_unit(&m_sla_object_menu);
|
|
append_menu_item_settings(&m_sla_object_menu);
|
|
//update_menu_items_instance_manipulation(mtObjectSLA);
|
|
append_menu_item_edit_text(&m_sla_object_menu);
|
|
append_menu_item_edit_svg(&m_object_menu);
|
|
|
|
return &m_sla_object_menu;
|
|
}
|
|
|
|
wxMenu* MenuFactory::part_menu()
|
|
{
|
|
append_menu_items_convert_unit(&m_part_menu);
|
|
append_menu_item_change_filament(&m_part_menu);
|
|
append_menu_item_precise_seam_submenu(&m_part_menu);
|
|
append_menu_item_per_object_settings(&m_part_menu);
|
|
return &m_part_menu;
|
|
}
|
|
|
|
wxMenu* MenuFactory::text_part_menu()
|
|
{
|
|
append_menu_item_change_filament(&m_text_part_menu);
|
|
append_menu_item_per_object_settings(&m_text_part_menu);
|
|
|
|
return &m_text_part_menu;
|
|
}
|
|
|
|
wxMenu *MenuFactory::svg_part_menu()
|
|
{
|
|
append_menu_item_change_filament(&m_svg_part_menu);
|
|
append_menu_item_per_object_settings(&m_svg_part_menu);
|
|
|
|
return &m_svg_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()
|
|
{
|
|
//BBS
|
|
wxDataViewItemArray sels;
|
|
obj_list()->GetSelections(sels);
|
|
bool multi_volume = true;
|
|
bool undefined_type = false;
|
|
bool all_plates = true;
|
|
|
|
for (const wxDataViewItem& item : sels) {
|
|
Slic3r::GUI::ItemType item_type = list_model()->GetItemType(item);
|
|
if ((item_type & itPlate) == 0)
|
|
all_plates = false;
|
|
multi_volume = item_type & itVolume;
|
|
if (!(item_type & (itVolume | itObject | itInstance)))
|
|
// show this menu only for Objects(Instances mixed with Objects)/Volumes selection
|
|
undefined_type = true;
|
|
}
|
|
|
|
if (all_plates) {
|
|
wxMenu* menu = new MenuWithSeparators();
|
|
append_menu_item_replace_all_with_stl(menu);
|
|
return menu;
|
|
}
|
|
if (undefined_type)
|
|
return nullptr;
|
|
wxMenu* menu = new MenuWithSeparators();
|
|
if (!multi_volume) {
|
|
int index = 0;
|
|
if (obj_list()->can_merge_to_multipart_object()) {
|
|
append_menu_item_merge_to_multipart_object(menu);
|
|
index++;
|
|
}
|
|
append_menu_item_center(menu);
|
|
append_menu_item_drop(menu);
|
|
append_menu_item_fix_through_cgal(menu);
|
|
//append_menu_item_simplify(menu);
|
|
append_menu_item_delete(menu);
|
|
menu->AppendSeparator();
|
|
|
|
append_menu_item_set_printable(menu);
|
|
menu->AppendSeparator();
|
|
|
|
append_menu_item_set_auto_drop(menu);
|
|
menu->AppendSeparator();
|
|
|
|
append_menu_item_per_object_process(menu);
|
|
menu->AppendSeparator();
|
|
|
|
append_menu_items_convert_unit(menu);
|
|
append_menu_item_replace_all_with_stl(menu);
|
|
//BBS
|
|
append_menu_item_change_filament(menu);
|
|
menu->AppendSeparator();
|
|
append_menu_item_export_stl(menu, true);
|
|
append_menu_item_export_drc(menu, true);
|
|
}
|
|
else {
|
|
append_menu_item_center(menu);
|
|
append_menu_item_drop(menu);
|
|
append_menu_item_fix_through_cgal(menu);
|
|
//append_menu_item_simplify(menu);
|
|
append_menu_item_delete(menu);
|
|
append_menu_items_convert_unit(menu);
|
|
append_menu_item_replace_all_with_stl(menu);
|
|
append_menu_item_change_filament(menu);
|
|
wxMenu* split_menu = new wxMenu();
|
|
if (split_menu) {
|
|
append_menu_item(split_menu, wxID_ANY, _L("To Objects"), _L("Split the selected object into multiple objects"),
|
|
[](wxCommandEvent&) { plater()->split_object(); }, "menu_split_objects", menu,
|
|
[]() { return plater()->can_split(true); }, m_parent);
|
|
append_menu_item(split_menu, wxID_ANY, _L("To Parts"), _L("Split the selected object into multiple parts"),
|
|
[](wxCommandEvent&) { plater()->split_volume(); }, "menu_split_parts", menu,
|
|
[]() { return plater()->can_split(false); }, m_parent);
|
|
|
|
append_submenu(menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "",
|
|
[]() { return plater()->can_split(true); }, m_parent);
|
|
}
|
|
append_menu_item_per_object_process(menu);
|
|
menu->AppendSeparator();
|
|
append_menu_item_change_type(menu);
|
|
// Subtype picker for Precise Seam — shown only when all selected volumes are PS.
|
|
// Must be paired with Change Type here (as in single-volume part_menu), otherwise
|
|
// the user cannot switch PS subtypes (LEFT/RIGHT/etc.) on multi-selection.
|
|
append_menu_item_precise_seam_submenu(menu);
|
|
append_menu_item_change_filament(menu);
|
|
}
|
|
return menu;
|
|
}
|
|
|
|
wxMenu* MenuFactory::assemble_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_set_visible(menu);
|
|
//append_menu_item_fix_through_cgal(menu);
|
|
//append_menu_item_simplify(menu);
|
|
append_menu_item_delete(menu);
|
|
menu->AppendSeparator();
|
|
append_menu_item_change_extruder(menu);
|
|
return menu;
|
|
}
|
|
|
|
|
|
//PS
|
|
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(); }, "", 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(); }, "", 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(); }, "", 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);
|
|
}
|
|
|
|
wxMenu *MenuFactory::filament_action_menu(int active_filament_menu_id) {
|
|
create_filament_action_menu(false, active_filament_menu_id);
|
|
return &m_filament_action_menu;
|
|
}
|
|
|
|
|
|
//BBS: add partplate related logic
|
|
wxMenu* MenuFactory::plate_menu()
|
|
{
|
|
append_menu_item_locked(&m_plate_menu);
|
|
append_menu_item_plate_name(&m_plate_menu);
|
|
return &m_plate_menu;
|
|
}
|
|
|
|
wxMenu* MenuFactory::assemble_object_menu()
|
|
{
|
|
wxMenu* menu = new MenuWithSeparators();
|
|
// Set Visible
|
|
append_menu_item_set_visible(menu);
|
|
// Delete
|
|
append_menu_item_delete(menu);
|
|
//// Object Repair
|
|
//append_menu_item_fix_through_cgal(menu);
|
|
//// Object Simplify
|
|
//append_menu_item_simplify(menu);
|
|
menu->AppendSeparator();
|
|
|
|
// Set filament
|
|
append_menu_item_change_extruder(menu);
|
|
//// Enter per object parameters
|
|
//append_menu_item_per_object_settings(menu);
|
|
return menu;
|
|
}
|
|
|
|
wxMenu* MenuFactory::assemble_part_menu()
|
|
{
|
|
wxMenu* menu = new MenuWithSeparators();
|
|
|
|
append_menu_item_set_visible(menu);
|
|
append_menu_item_delete(menu);
|
|
//append_menu_item_simplify(menu);
|
|
menu->AppendSeparator();
|
|
|
|
append_menu_item_change_extruder(menu);
|
|
//append_menu_item_per_object_settings(menu);
|
|
return menu;
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_clone(wxMenu* menu)
|
|
{
|
|
const std::string accel = wxGetApp().shortcuts().accelerator(Shortcut::CloneSelected);
|
|
append_menu_item(menu, wxID_ANY, _L("Clone") + (accel.empty() ? wxString() : "\t" + from_u8(accel)), "",
|
|
[](wxCommandEvent&) {
|
|
plater()->clone_selection();
|
|
}, "", nullptr,
|
|
[]() {
|
|
return true;
|
|
}, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_simplify(wxMenu* menu)
|
|
{
|
|
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Simplify Model"), "",
|
|
[](wxCommandEvent&) { obj_list()->simplify(); }, "", menu,
|
|
[]() {return plater()->can_simplify(); }, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_smooth_mesh(wxMenu *menu)
|
|
{
|
|
wxMenuItem *menu_item = append_menu_item(
|
|
menu, wxID_ANY, _L("Subdivision mesh") + _L("(Lost color)"), "", [](wxCommandEvent &) { obj_list()->smooth_mesh(); }, "", menu, []() { return plater()->can_smooth_mesh(); },
|
|
m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_center(wxMenu* menu)
|
|
{
|
|
append_menu_item(menu, wxID_ANY, _L("Center") , "",
|
|
[](wxCommandEvent&) {
|
|
plater()->center_selection();
|
|
}, "", nullptr,
|
|
[]() {
|
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
|
return false;
|
|
else {
|
|
Selection& selection = plater()->get_view3D_canvas3D()->get_selection();
|
|
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
|
Vec3d model_pos = selection.get_bounding_box().center();
|
|
Vec3d center_pos = plate->get_center_origin();
|
|
return !( (model_pos.x() == center_pos.x()) && (model_pos.y() == center_pos.y()) );
|
|
} //disable if model is at center / not in View3D
|
|
}, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_drop(wxMenu* menu)
|
|
{
|
|
append_menu_item(menu, wxID_ANY, _L("Drop") , "",
|
|
[](wxCommandEvent&) {
|
|
plater()->drop_selection();
|
|
}, "", nullptr,
|
|
[]() {
|
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
|
return false;
|
|
else {
|
|
return (std::abs(plater()->get_view3D_canvas3D()->get_selection().get_bounding_box().min.z()) > -SINKING_Z_THRESHOLD);
|
|
} //disable if model is on the bed / not in View3D
|
|
}, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_per_object_process(wxMenu* menu)
|
|
{
|
|
const std::vector<wxString> names = { _L("Edit Process Settings"), _L("Edit Process Settings") };
|
|
append_menu_item(menu, wxID_ANY, names[0], names[1],
|
|
[](wxCommandEvent&) {
|
|
wxGetApp().obj_list()->switch_to_object_process();
|
|
}, "", nullptr,
|
|
[]() {
|
|
Selection& selection = plater()->canvas3D()->get_selection();
|
|
return selection.is_single_full_object() ||
|
|
selection.is_multiple_full_object() ||
|
|
selection.is_single_full_instance() ||
|
|
selection.is_multiple_full_instance() ||
|
|
selection.is_single_volume() ||
|
|
selection.is_multiple_volume();
|
|
}, m_parent);
|
|
|
|
const std::vector<wxString> names2 = {_L("Copy Process Settings"), _L("Copy Process Settings")};
|
|
append_menu_item(
|
|
menu, wxID_ANY, names2[0], names2[1], [](wxCommandEvent &) {
|
|
wxGetApp().obj_list()->copy_settings_to_clipboard();
|
|
}, "", nullptr,
|
|
[]() {
|
|
Selection &selection = plater()->canvas3D()->get_selection();
|
|
return selection.is_single_full_object() || selection.is_single_full_instance() ||
|
|
selection.is_single_volume_or_modifier();
|
|
},
|
|
m_parent);
|
|
|
|
const std::vector<wxString> names3 = {_L("Paste Process Settings"), _L("Paste Process Settings")};
|
|
append_menu_item(
|
|
menu, wxID_ANY, names3[0], names3[1], [](wxCommandEvent &) {
|
|
wxGetApp().obj_list()->paste_settings_into_list();
|
|
}, "", nullptr,
|
|
[]() {
|
|
return wxGetApp().obj_list()->can_paste_settings_into_list();
|
|
},
|
|
m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_per_object_settings(wxMenu* menu)
|
|
{
|
|
const std::vector<wxString> names = { _L("Edit in Parameter Table"), _L("Edit print parameters for a single object") };
|
|
// 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);
|
|
}
|
|
|
|
append_menu_item(menu, wxID_ANY, names[0], names[1],
|
|
[](wxCommandEvent&) {
|
|
plater()->PopupObjectTableBySelection();
|
|
}, "", nullptr,
|
|
[]() {
|
|
Selection& selection = plater()->canvas3D()->get_selection();
|
|
return selection.is_single_full_object() || selection.is_single_full_instance() || selection.is_single_volume();
|
|
}, m_parent);
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_change_filament(wxMenu* menu)
|
|
{
|
|
const std::vector<wxString> names = { _L("Change Filament"), _L("Set Filament 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);
|
|
}
|
|
|
|
int filaments_cnt = filaments_count();
|
|
if (filaments_cnt <= 1)
|
|
return;
|
|
|
|
wxDataViewItemArray sels;
|
|
obj_list()->GetSelections(sels);
|
|
if (sels.IsEmpty())
|
|
return;
|
|
|
|
if (sels.Count() == 1) {
|
|
const auto sel_vol = obj_list()->get_selected_model_volume();
|
|
if (sel_vol && sel_vol->type() != ModelVolumeType::MODEL_PART && sel_vol->type() != ModelVolumeType::PARAMETER_MODIFIER)
|
|
return;
|
|
}
|
|
|
|
std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
|
|
if (icons.size() < filaments_cnt) {
|
|
BOOST_LOG_TRIVIAL(warning) << boost::format("Warning: icons size %1%, filaments_cnt=%2%")%icons.size()%filaments_cnt;
|
|
if (icons.size() <= 1)
|
|
return;
|
|
else
|
|
filaments_cnt = icons.size();
|
|
}
|
|
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]);
|
|
// BBS
|
|
const auto sel_vol = obj_list()->get_selected_model_volume();
|
|
if (sel_vol && sel_vol->type() == ModelVolumeType::PARAMETER_MODIFIER)
|
|
initial_extruder = config.has("extruder") ? config.extruder() : 0;
|
|
else
|
|
initial_extruder = config.has("extruder") ? config.extruder() : 1;
|
|
}
|
|
|
|
// BBS
|
|
bool has_modifier = false;
|
|
for (auto sel : sels) {
|
|
if (obj_list()->GetModel()->GetVolumeType(sel) == ModelVolumeType::PARAMETER_MODIFIER) {
|
|
has_modifier = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = has_modifier ? 0 : 1; i <= filaments_cnt; i++)
|
|
{
|
|
// BBS
|
|
//bool is_active_extruder = i == initial_extruder;
|
|
bool is_active_extruder = false;
|
|
|
|
wxString item_name = _L("Default");
|
|
|
|
if (i > 0) {
|
|
auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i - 1]);
|
|
if (preset == nullptr) {
|
|
item_name = wxString::Format(_L("Filament %d"), i);
|
|
} else {
|
|
item_name = from_u8(preset->label(false));
|
|
}
|
|
}
|
|
|
|
if (is_active_extruder) {
|
|
item_name << " (" + _L("current") + ")";
|
|
}
|
|
|
|
append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "",
|
|
[i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, i == 0 ? wxNullBitmap : *icons[i - 1], menu,
|
|
[is_active_extruder]() { return !is_active_extruder; }, m_parent);
|
|
}
|
|
menu->Append(wxID_ANY, name, extruder_selection_menu, _L("Change Filament"));
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_set_printable(wxMenu* menu)
|
|
{
|
|
const Selection& selection = plater()->canvas3D()->get_selection();
|
|
bool all_printable = true;
|
|
ObjectList* list = obj_list();
|
|
wxDataViewItemArray sels;
|
|
list->GetSelections(sels);
|
|
|
|
for (wxDataViewItem item : sels) {
|
|
ItemType type = list->GetModel()->GetItemType(item);
|
|
if (type != itInstance && type != itObject)
|
|
continue;
|
|
else {
|
|
int obj_idx = list->GetModel()->GetObjectIdByItem(item);
|
|
int inst_idx = type == itObject ? 0 : list->GetModel()->GetInstanceIdByItem(item);
|
|
all_printable &= list->object(obj_idx)->instances[inst_idx]->printable;
|
|
}
|
|
}
|
|
|
|
wxMenuItem* menu_item_set_printable = append_menu_check_item(menu, wxID_ANY, _L("Printable") + "\t" + "V", "", [all_printable](wxCommandEvent&) {
|
|
Selection& selection = plater()->canvas3D()->get_selection();
|
|
selection.set_printable(!all_printable);
|
|
}, menu);
|
|
m_parent->Bind(wxEVT_UPDATE_UI, [all_printable](wxUpdateUIEvent& evt) {
|
|
evt.Check(all_printable);
|
|
plater()->set_current_canvas_as_dirty();
|
|
|
|
}, menu_item_set_printable->GetId());
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_set_auto_drop(wxMenu* menu)
|
|
{
|
|
const Selection& selection = plater()->canvas3D()->get_selection();
|
|
const bool current_auto_drop = selection.get_auto_drop();
|
|
|
|
wxString menu_text = _L("Auto Drop");
|
|
wxString menu_tooltip = _L("Automatically snaps the selected object to the build plate.");
|
|
wxMenuItem* menu_item_set_auto_drop = append_menu_check_item(
|
|
menu, wxID_ANY, menu_text, menu_tooltip,
|
|
[current_auto_drop](wxCommandEvent&) {
|
|
Selection& selection = plater()->canvas3D()->get_selection();
|
|
selection.set_auto_drop(!current_auto_drop);
|
|
},
|
|
menu);
|
|
m_parent->Bind(
|
|
wxEVT_UPDATE_UI,
|
|
[current_auto_drop](wxUpdateUIEvent& evt) {
|
|
evt.Check(current_auto_drop);
|
|
plater()->set_current_canvas_as_dirty();
|
|
},
|
|
menu_item_set_auto_drop->GetId());
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_locked(wxMenu* menu)
|
|
{
|
|
const std::vector<wxString> names = { _L("Unlock"), _L("Lock") };
|
|
// 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);
|
|
}
|
|
|
|
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
wxString lock_text = plate->is_locked() ? names[0] : names[1];
|
|
|
|
auto item = append_menu_item(menu, wxID_ANY, lock_text, "",
|
|
[plate](wxCommandEvent&) {
|
|
bool lock = plate->is_locked();
|
|
plate->lock(!lock);
|
|
}, "", nullptr, []() { return true; }, m_parent);
|
|
|
|
m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
|
|
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
//bool check = plate->is_locked();
|
|
//evt.Check(check);
|
|
plater()->set_current_canvas_as_dirty();
|
|
}, item->GetId());
|
|
}
|
|
|
|
void MenuFactory::append_menu_item_plate_name(wxMenu *menu)
|
|
{
|
|
wxString name= _L("Edit Plate Name");
|
|
// Delete old menu item
|
|
const int item_id = menu->FindItem(name);
|
|
if (item_id != wxNOT_FOUND) menu->Destroy(item_id);
|
|
|
|
PartPlate *plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
|
|
auto item = append_menu_item(
|
|
menu, wxID_ANY, name, "",
|
|
[](wxCommandEvent &e) {
|
|
int hover_idx =plater()->canvas3D()->GetHoverId();
|
|
if (hover_idx == -1) {
|
|
int plate_idx=plater()->GetPlateIndexByRightMenuInLeftUI();
|
|
plater()->select_plate_by_hover_id(plate_idx * PartPlate::GRABBER_COUNT, false, true);
|
|
}
|
|
else
|
|
{
|
|
plater()->select_plate_by_hover_id(hover_idx, false, true);
|
|
}
|
|
plater()->get_current_canvas3D()->post_event(SimpleEvent(EVT_GLCANVAS_PLATE_NAME_CHANGE));
|
|
},
|
|
"", nullptr, []() { return true; }, m_parent);
|
|
|
|
m_parent->Bind(
|
|
wxEVT_UPDATE_UI,
|
|
[](wxUpdateUIEvent &evt) {
|
|
PartPlate *plate = plater()->get_partplate_list().get_selected_plate();
|
|
assert(plate);
|
|
plater()->set_current_canvas_as_dirty();
|
|
},
|
|
item->GetId());
|
|
}
|
|
|
|
void MenuFactory::update_object_menu()
|
|
{
|
|
append_menu_items_add_volume(&m_object_menu);
|
|
}
|
|
|
|
void MenuFactory::update_default_menu()
|
|
{
|
|
for (auto& name : { _L("Add Primitive") , _L("Add Handy models"), _L("Show Labels") }) {
|
|
const auto menu_item_id = m_default_menu.FindItem(name);
|
|
if (menu_item_id != wxNOT_FOUND)
|
|
m_default_menu.Destroy(menu_item_id);
|
|
}
|
|
create_default_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));
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// For this class is used code from stackoverflow:
|
|
// https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence
|
|
// Using this code we can to inspect of an existence of IsWheelInverted() function in class T
|
|
template <typename T>
|
|
class menu_has_update_def_colors
|
|
{
|
|
typedef char one;
|
|
struct two { char x[2]; };
|
|
|
|
template <typename C> static one test(decltype(&C::UpdateDefColors));
|
|
template <typename C> static two test(...);
|
|
|
|
public:
|
|
static constexpr bool value = sizeof(test<T>(0)) == sizeof(char);
|
|
};
|
|
|
|
template<typename T>
|
|
static void update_menu_item_def_colors(T* item)
|
|
{
|
|
if constexpr (menu_has_update_def_colors<wxMenuItem>::value) {
|
|
item->UpdateDefColors();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void MenuFactory::sys_color_changed()
|
|
{
|
|
for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu }) {
|
|
msw_rescale_menu(dynamic_cast<wxMenu*>(menu));// msw_rescale_menu updates just icons, so use it
|
|
#ifdef _WIN32
|
|
// but under MSW we have to update item's bachground color
|
|
for (wxMenuItem* item : menu->GetMenuItems())
|
|
update_menu_item_def_colors(item);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void MenuFactory::sys_color_changed(wxMenuBar* menubar)
|
|
{
|
|
// BBS: fix
|
|
#if 0
|
|
for (size_t id = 0; id < menubar->GetMenuCount(); id++) {
|
|
wxMenu* menu = menubar->GetMenu(id);
|
|
msw_rescale_menu(menu);
|
|
#ifdef _WIN32
|
|
// but under MSW we have to update item's bachground color
|
|
for (wxMenuItem* item : menu->GetMenuItems())
|
|
update_menu_item_def_colors(item);
|
|
#endif
|
|
}
|
|
menubar->Refresh();
|
|
#endif
|
|
}
|
|
|
|
|
|
} //namespace GUI
|
|
} //namespace Slic3r
|