mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-14 00:52:04 +00:00
Allow 'Change Type' to be used with Multiple Parts Selected (#11544)
* Initial changes allowing you to change the type of multiple parts at once by selecting them all. * Removed second occurance of Change type in right click menu * Ready to go feature change * Remove accidental file creation * Removing excessive std::cerr --------- Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
@@ -751,8 +751,14 @@ wxMenuItem* MenuFactory::append_menu_item_change_type(wxMenu* menu)
|
||||
return append_menu_item(menu, wxID_ANY, _L("Change type"), "",
|
||||
[](wxCommandEvent&) { obj_list()->change_part_type(); }, "", menu,
|
||||
[]() {
|
||||
wxDataViewItem item = obj_list()->GetSelection();
|
||||
return item.IsOk() || obj_list()->GetModel()->GetItemType(item) == itVolume;
|
||||
wxDataViewItemArray selections;
|
||||
obj_list()->GetSelections(selections);
|
||||
if (selections.empty()) return false;
|
||||
for (const auto& it : selections) {
|
||||
if (!(obj_list()->GetModel()->GetItemType(it) & itVolume))
|
||||
return false; // non-volume present -> disable
|
||||
}
|
||||
return true;
|
||||
}, m_parent);
|
||||
}
|
||||
|
||||
@@ -1798,6 +1804,7 @@ wxMenu* MenuFactory::multi_selection_menu()
|
||||
}
|
||||
append_menu_item_per_object_process(menu);
|
||||
menu->AppendSeparator();
|
||||
append_menu_item_change_type(menu);
|
||||
append_menu_item_change_filament(menu);
|
||||
}
|
||||
return menu;
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
#include "SingleChoiceDialog.hpp"
|
||||
#include "StepMeshDialog.hpp"
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/listbook.h>
|
||||
@@ -5337,26 +5341,32 @@ ModelVolume* ObjectList::get_selected_model_volume()
|
||||
|
||||
void ObjectList::change_part_type()
|
||||
{
|
||||
ModelVolume* volume = get_selected_model_volume();
|
||||
if (!volume)
|
||||
return;
|
||||
wxDataViewItemArray selections;
|
||||
GetSelections(selections);
|
||||
|
||||
const int obj_idx = get_selected_obj_idx();
|
||||
if (obj_idx < 0) return;
|
||||
if (selections.size() <= 1) {
|
||||
int obj_idx = get_selected_obj_idx();
|
||||
if (obj_idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModelVolume* volume = get_selected_model_volume();
|
||||
if (!volume) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ModelVolumeType type = volume->type();
|
||||
if (type == ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
int model_part_cnt = 0;
|
||||
for (auto vol : (*m_objects)[obj_idx]->volumes) {
|
||||
if (vol->type() == ModelVolumeType::MODEL_PART)
|
||||
++model_part_cnt;
|
||||
}
|
||||
if (type == ModelVolumeType::MODEL_PART) {
|
||||
int model_part_cnt = 0;
|
||||
for (auto vol : (*m_objects)[obj_idx]->volumes) {
|
||||
if (vol->type() == ModelVolumeType::MODEL_PART)
|
||||
++model_part_cnt;
|
||||
}
|
||||
|
||||
if (model_part_cnt == 1) {
|
||||
Slic3r::GUI::show_error(nullptr, _(L("The type of the last solid object part is not to be changed.")));
|
||||
return;
|
||||
}
|
||||
if (model_part_cnt == 1) {
|
||||
Slic3r::GUI::show_error(nullptr, _(L("The type of the last solid object part is not to be changed.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Fix crash when changing type of svg / text modifier
|
||||
@@ -5365,22 +5375,165 @@ void ObjectList::change_part_type()
|
||||
names.Add(_L("Negative Part"));
|
||||
names.Add(_L("Modifier"));
|
||||
if (!volume->is_svg() && !volume->is_text()) {
|
||||
names.Add(_L("Support Blocker"));
|
||||
names.Add(_L("Support Enforcer"));
|
||||
names.Add(_L("Support Blocker"));
|
||||
names.Add(_L("Support Enforcer"));
|
||||
}
|
||||
|
||||
SingleChoiceDialog dlg(_L("Type:"), _L("Choose part type"), names, int(type));
|
||||
auto new_type = ModelVolumeType(dlg.GetSingleChoiceIndex());
|
||||
|
||||
if (new_type == type || new_type == ModelVolumeType::INVALID)
|
||||
return;
|
||||
if (new_type == type || new_type == ModelVolumeType::INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
take_snapshot("Change part type");
|
||||
|
||||
volume->set_type(new_type);
|
||||
wxDataViewItemArray sel = reorder_volumes_and_get_selection(obj_idx, [volume](const ModelVolume* vol) { return vol == volume; });
|
||||
if (!sel.IsEmpty())
|
||||
select_item(sel.front());
|
||||
if (!sel.IsEmpty()) {
|
||||
select_item(sel.front());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Multi Selection ---
|
||||
struct Target { int obj_idx; Slic3r::ModelVolume* vol; };
|
||||
std::vector<Target> targets;
|
||||
targets.reserve(selections.size());
|
||||
bool any_text_or_svg = false;
|
||||
|
||||
for (const auto& item : selections) {
|
||||
auto typeMask = m_objects_model->GetItemType(item);
|
||||
if (!(typeMask & itVolume)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int obj_idx = -1, vol_idx = -1;
|
||||
get_selected_item_indexes(obj_idx, vol_idx, item);
|
||||
if (obj_idx < 0 || vol_idx < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ModelVolume* vol = (*m_objects)[obj_idx]->volumes[vol_idx];
|
||||
if (!vol) {
|
||||
continue;
|
||||
}
|
||||
|
||||
targets.push_back({ obj_idx, vol });
|
||||
if (vol->is_svg() || vol->is_text())
|
||||
any_text_or_svg = true;
|
||||
}
|
||||
|
||||
if (targets.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxArrayString names;
|
||||
names.Add(_L("Part"));
|
||||
names.Add(_L("Negative Part"));
|
||||
names.Add(_L("Modifier"));
|
||||
if (!any_text_or_svg) {
|
||||
names.Add(_L("Support Blocker"));
|
||||
names.Add(_L("Support Enforcer"));
|
||||
}
|
||||
|
||||
// Preselect current type of the first selected volume
|
||||
ModelVolumeType initial_type = targets.front().vol->type();
|
||||
SingleChoiceDialog dlg(_L("Type:"), _L("Choose part type"), names, int(initial_type));
|
||||
auto new_type = ModelVolumeType(dlg.GetSingleChoiceIndex());
|
||||
if (new_type == ModelVolumeType::INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (new_type != ModelVolumeType::MODEL_PART) {
|
||||
// Count initial MODEL_PARTs per object
|
||||
std::unordered_map<int, int> parts_initial;
|
||||
for (const auto& t : targets) {
|
||||
int cnt = 0;
|
||||
for (auto v : (*m_objects)[t.obj_idx]->volumes)
|
||||
if (v->type() == ModelVolumeType::MODEL_PART) ++cnt;
|
||||
parts_initial[t.obj_idx] = cnt;
|
||||
}
|
||||
|
||||
// Count how many selected MODEL_PARTs would be converted away, per object
|
||||
std::unordered_map<int, int> parts_to_remove;
|
||||
for (const auto& t : targets) {
|
||||
if (t.vol->type() == ModelVolumeType::MODEL_PART) {
|
||||
++parts_to_remove[t.obj_idx];
|
||||
}
|
||||
}
|
||||
|
||||
// If for any object: initial_parts > 0 and removals == initial_parts => would remove all
|
||||
bool would_remove_all_for_any = false;
|
||||
for (const auto& kv : parts_to_remove) {
|
||||
const int obj_idx = kv.first;
|
||||
const int removing = kv.second;
|
||||
const int initial = parts_initial[obj_idx];
|
||||
if (initial > 0 && removing == initial) {
|
||||
would_remove_all_for_any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (would_remove_all_for_any) {
|
||||
Slic3r::GUI::show_error(nullptr, _(L("The type of the last solid object part is not to be changed.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
take_snapshot("Change part type (multi)");
|
||||
|
||||
// Apply changes
|
||||
size_t applied = 0, skipped_same = 0;
|
||||
std::unordered_map<int, std::vector<ModelVolume*>> changed_per_object;
|
||||
for (const auto& t : targets) {
|
||||
const auto current = t.vol->type();
|
||||
if (current == new_type) {
|
||||
++skipped_same;
|
||||
continue;
|
||||
}
|
||||
t.vol->set_type(new_type);
|
||||
changed_per_object[t.obj_idx].push_back(t.vol);
|
||||
++applied;
|
||||
}
|
||||
|
||||
if (applied == 0) {
|
||||
// Nothing changed; keep the original selection as-is
|
||||
select_items(selections);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reorder per object and rebuild selection to follow changed volumes
|
||||
wxDataViewItemArray new_selection;
|
||||
for (const auto& kv : changed_per_object) {
|
||||
const int obj_idx = kv.first;
|
||||
const auto& changed_vols = kv.second;
|
||||
|
||||
std::unordered_set<const ModelVolume*> changed_set;
|
||||
changed_set.reserve(changed_vols.size());
|
||||
for (const auto* v : changed_vols) {
|
||||
changed_set.insert(v);
|
||||
}
|
||||
|
||||
wxDataViewItemArray sel = reorder_volumes_and_get_selection(
|
||||
obj_idx,
|
||||
[&changed_set](const ModelVolume* v) -> bool {
|
||||
return changed_set.find(v) != changed_set.end();
|
||||
}
|
||||
);
|
||||
|
||||
// Append to new_selection
|
||||
for (const auto& it : sel) new_selection.Add(it);
|
||||
}
|
||||
|
||||
if (!new_selection.IsEmpty()) {
|
||||
select_items(new_selection);
|
||||
} else {
|
||||
select_items(selections);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void ObjectList::last_volume_is_deleted(const int obj_idx)
|
||||
|
||||
Reference in New Issue
Block a user