mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-20 20:03:47 +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"), "",
|
return append_menu_item(menu, wxID_ANY, _L("Change type"), "",
|
||||||
[](wxCommandEvent&) { obj_list()->change_part_type(); }, "", menu,
|
[](wxCommandEvent&) { obj_list()->change_part_type(); }, "", menu,
|
||||||
[]() {
|
[]() {
|
||||||
wxDataViewItem item = obj_list()->GetSelection();
|
wxDataViewItemArray selections;
|
||||||
return item.IsOk() || obj_list()->GetModel()->GetItemType(item) == itVolume;
|
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);
|
}, m_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1798,6 +1804,7 @@ wxMenu* MenuFactory::multi_selection_menu()
|
|||||||
}
|
}
|
||||||
append_menu_item_per_object_process(menu);
|
append_menu_item_per_object_process(menu);
|
||||||
menu->AppendSeparator();
|
menu->AppendSeparator();
|
||||||
|
append_menu_item_change_type(menu);
|
||||||
append_menu_item_change_filament(menu);
|
append_menu_item_change_filament(menu);
|
||||||
}
|
}
|
||||||
return menu;
|
return menu;
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
#include "SingleChoiceDialog.hpp"
|
#include "SingleChoiceDialog.hpp"
|
||||||
#include "StepMeshDialog.hpp"
|
#include "StepMeshDialog.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <functional>
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <wx/progdlg.h>
|
#include <wx/progdlg.h>
|
||||||
#include <wx/listbook.h>
|
#include <wx/listbook.h>
|
||||||
@@ -5337,16 +5341,22 @@ ModelVolume* ObjectList::get_selected_model_volume()
|
|||||||
|
|
||||||
void ObjectList::change_part_type()
|
void ObjectList::change_part_type()
|
||||||
{
|
{
|
||||||
ModelVolume* volume = get_selected_model_volume();
|
wxDataViewItemArray selections;
|
||||||
if (!volume)
|
GetSelections(selections);
|
||||||
return;
|
|
||||||
|
|
||||||
const int obj_idx = get_selected_obj_idx();
|
if (selections.size() <= 1) {
|
||||||
if (obj_idx < 0) return;
|
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();
|
const ModelVolumeType type = volume->type();
|
||||||
if (type == ModelVolumeType::MODEL_PART)
|
if (type == ModelVolumeType::MODEL_PART) {
|
||||||
{
|
|
||||||
int model_part_cnt = 0;
|
int model_part_cnt = 0;
|
||||||
for (auto vol : (*m_objects)[obj_idx]->volumes) {
|
for (auto vol : (*m_objects)[obj_idx]->volumes) {
|
||||||
if (vol->type() == ModelVolumeType::MODEL_PART)
|
if (vol->type() == ModelVolumeType::MODEL_PART)
|
||||||
@@ -5372,15 +5382,158 @@ void ObjectList::change_part_type()
|
|||||||
SingleChoiceDialog dlg(_L("Type:"), _L("Choose part type"), names, int(type));
|
SingleChoiceDialog dlg(_L("Type:"), _L("Choose part type"), names, int(type));
|
||||||
auto new_type = ModelVolumeType(dlg.GetSingleChoiceIndex());
|
auto new_type = ModelVolumeType(dlg.GetSingleChoiceIndex());
|
||||||
|
|
||||||
if (new_type == type || new_type == ModelVolumeType::INVALID)
|
if (new_type == type || new_type == ModelVolumeType::INVALID) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
take_snapshot("Change part type");
|
take_snapshot("Change part type");
|
||||||
|
|
||||||
volume->set_type(new_type);
|
volume->set_type(new_type);
|
||||||
wxDataViewItemArray sel = reorder_volumes_and_get_selection(obj_idx, [volume](const ModelVolume* vol) { return vol == volume; });
|
wxDataViewItemArray sel = reorder_volumes_and_get_selection(obj_idx, [volume](const ModelVolume* vol) { return vol == volume; });
|
||||||
if (!sel.IsEmpty())
|
if (!sel.IsEmpty()) {
|
||||||
select_item(sel.front());
|
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)
|
void ObjectList::last_volume_is_deleted(const int obj_idx)
|
||||||
|
|||||||
Reference in New Issue
Block a user