Skip to content

Commit f1e4f2a

Browse files
authored
Merge pull request #54 from PartStackerCommunity/parts
Make parts lists more ergonomic
2 parents c5cd7d9 + 4826331 commit f1e4f2a

File tree

6 files changed

+122
-56
lines changed

6 files changed

+122
-56
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
on:
22
workflow_dispatch:
33
workflow_call:
4+
pull_request:
45

56
jobs:
67
test-windows:

src/pstack/gui/controls.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,23 @@ void controls::initialize(main_window* parent) {
5454
minimize_text = new wxStaticText(panel, wxID_ANY, "Minimize box:");
5555
quantity_spinner = new wxSpinCtrl(panel);
5656
quantity_spinner->SetRange(0, 200);
57+
quantity_spinner->Disable();
5758
min_hole_spinner = new wxSpinCtrl(panel);
5859
min_hole_spinner->SetRange(0, 100);
59-
minimize_checkbox = new wxCheckBox(panel, wxID_ANY, "");
60+
min_hole_spinner->Disable();
61+
minimize_checkbox = new wxCheckBox(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxCHK_3STATE);
62+
minimize_checkbox->Disable();
6063
wxArrayString rotation_choices;
6164
rotation_choices.Add("None");
6265
rotation_choices.Add("Cubic");
6366
rotation_choices.Add("Arbitrary");
6467
rotation_text = new wxStaticText(panel, wxID_ANY, "Rotations:");
6568
rotation_dropdown = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, rotation_choices);
69+
rotation_dropdown->Disable();
6670
preview_voxelization_button = new wxButton(panel, wxID_ANY, "Preview voxelization");
71+
preview_voxelization_button->Disable();
6772
preview_bounding_box_button = new wxButton(panel, wxID_ANY, "Preview bounding box");
73+
preview_bounding_box_button->Disable();
6874
}
6975

7076
{

src/pstack/gui/main_window.cpp

Lines changed: 93 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ main_window::main_window(const wxString& title)
2727
_parts_list.initialize(_controls.notebook_panels[0]);
2828
_results_list.initialize(_controls.notebook_panels[2]);
2929
bind_all_controls();
30-
enable_part_settings(false);
3130

3231
wxGLAttributes attrs;
3332
attrs.PlatformDefaults().Defaults().EndList();
@@ -45,33 +44,71 @@ main_window::main_window(const wxString& title)
4544
}
4645

4746
void main_window::on_select_parts(const std::vector<std::size_t>& indices) {
48-
const auto size = indices.size();
49-
_controls.delete_part_button->Enable(size != 0);
50-
_controls.reload_part_button->Enable(size != 0);
51-
_controls.copy_part_button->Enable(size == 1);
52-
_controls.mirror_part_button->Enable(size == 1);
53-
if (size == 1) {
54-
set_part(indices[0]);
55-
} else {
56-
unset_part();
47+
const bool any_selected = not indices.empty();
48+
_controls.delete_part_button->Enable(any_selected);
49+
_controls.reload_part_button->Enable(any_selected);
50+
_controls.copy_part_button->Enable(any_selected);
51+
_controls.mirror_part_button->Enable(any_selected);
52+
enable_part_settings(any_selected);
53+
_current_parts.clear();
54+
if (not any_selected) {
55+
return;
5756
}
58-
}
5957

60-
void main_window::set_part(const std::size_t index) {
61-
enable_part_settings(true);
62-
_current_part = _parts_list.at(index);
63-
_current_part_index.emplace(index);
64-
_controls.quantity_spinner->SetValue(_current_part->quantity);
65-
_controls.min_hole_spinner->SetValue(_current_part->min_hole);
66-
_controls.minimize_checkbox->SetValue(_current_part->rotate_min_box);
67-
_controls.rotation_dropdown->SetSelection(_current_part->rotation_index);
68-
_viewport->set_mesh(_current_part->mesh, _current_part->centroid);
69-
}
58+
std::optional<int> quantity{};
59+
std::optional<int> min_hole{};
60+
std::optional<bool> rotate_min_box{};
61+
std::optional<int> rotation_index{};
62+
bool first_time = true;
63+
for (const std::size_t index : indices) {
64+
calc::part& part = _parts_list.at(index);
65+
_current_parts.emplace_back(&part, index);
66+
if (first_time) {
67+
first_time = false;
68+
quantity.emplace(part.quantity);
69+
min_hole.emplace(part.min_hole);
70+
rotate_min_box.emplace(part.rotate_min_box);
71+
rotation_index.emplace(part.rotation_index);
72+
} else {
73+
if (quantity.has_value() and *quantity != part.quantity) {
74+
quantity.reset();
75+
}
76+
if (min_hole.has_value() and *min_hole != part.min_hole) {
77+
min_hole.reset();
78+
}
79+
if (rotate_min_box.has_value() and *rotate_min_box != part.rotate_min_box) {
80+
rotate_min_box.reset();
81+
}
82+
if (rotation_index.has_value() and *rotation_index != part.rotation_index) {
83+
rotation_index.reset();
84+
}
85+
}
86+
}
87+
if (quantity.has_value()) {
88+
_controls.quantity_spinner->SetValue(*quantity);
89+
} else {
90+
_controls.quantity_spinner->SetValue("");
91+
}
92+
if (min_hole.has_value()) {
93+
_controls.min_hole_spinner->SetValue(*min_hole);
94+
} else {
95+
_controls.min_hole_spinner->SetValue("");
96+
}
97+
if (rotate_min_box.has_value()) {
98+
_controls.minimize_checkbox->SetValue(*rotate_min_box);
99+
} else {
100+
_controls.minimize_checkbox->Set3StateValue(wxCHK_UNDETERMINED);
101+
}
102+
if (rotation_index.has_value()) {
103+
_controls.rotation_dropdown->SetSelection(*rotation_index);
104+
} else {
105+
_controls.rotation_dropdown->SetSelection(wxNOT_FOUND);
106+
}
70107

71-
void main_window::unset_part() {
72-
enable_part_settings(false);
73-
_current_part.reset();
74-
_current_part_index.reset();
108+
if (_current_parts.size() == 1) {
109+
const calc::part& part = *_current_parts[0].part;
110+
_viewport->set_mesh(part.mesh, part.centroid);
111+
}
75112
}
76113

77114
void main_window::enable_part_settings(bool enable) {
@@ -113,9 +150,7 @@ void main_window::on_switch_tab(wxBookCtrlEvent& event) {
113150
switch (event.GetSelection()) {
114151
case 0: {
115152
_parts_list.get_selected(selected);
116-
if (selected.size() == 1) {
117-
set_part(selected[0]);
118-
}
153+
on_select_parts(selected);
119154
break;
120155
}
121156
case 2: {
@@ -207,7 +242,7 @@ void main_window::on_stacking_success(calc::stack_result result, const std::chro
207242

208243
void main_window::enable_on_stacking(const bool starting) {
209244
const bool enable = not starting;
210-
enable_part_settings(enable and _current_part_index.has_value());
245+
enable_part_settings(enable and not _current_parts.empty());
211246
_parts_list.control()->Enable(enable);
212247
for (wxMenuItem* item : _disableable_menu_items) {
213248
item->Enable(enable);
@@ -328,7 +363,7 @@ wxMenuBar* main_window::make_menu_bar() {
328363

329364
auto preferences_menu = new wxMenu();
330365
preferences_menu->AppendCheckItem((int)menu_item::pref_scroll, "Invert &scroll", "Change the viewport scroll direction");
331-
preferences_menu->AppendCheckItem((int)menu_item::pref_extra, "Display &extra parts", "Display the extra part quantity separately");
366+
preferences_menu->AppendCheckItem((int)menu_item::pref_extra, "Display &extra parts", "Display the quantity of extra parts separately");
332367
menu_bar->Append(preferences_menu, "&Preferences");
333368

334369
auto help_menu = new wxMenu();
@@ -354,16 +389,23 @@ void main_window::bind_all_controls() {
354389
_controls.delete_part_button->Bind(wxEVT_BUTTON, &main_window::on_delete_part, this);
355390
_controls.reload_part_button->Bind(wxEVT_BUTTON, &main_window::on_reload_part, this);
356391
_controls.copy_part_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
357-
_parts_list.append(*_current_part);
392+
for (auto& current_part : _current_parts) {
393+
_parts_list.append(*current_part.part);
394+
}
358395
_parts_list.update_label();
359396
event.Skip();
360397
});
361398
_controls.mirror_part_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
362-
_current_part->mirrored = not _current_part->mirrored;
363-
_current_part->mesh.mirror_x();
364-
_current_part->mesh.set_baseline({ 0, 0, 0 });
365-
_parts_list.reload_text(_current_part_index.value());
366-
set_part(_current_part_index.value());
399+
static thread_local std::vector<std::size_t> indices{};
400+
indices.clear();
401+
for (auto& current_part : _current_parts) {
402+
indices.push_back(current_part.index);
403+
current_part.part->mirrored = not current_part.part->mirrored;
404+
current_part.part->mesh.mirror_x();
405+
current_part.part->mesh.set_baseline({ 0, 0, 0 });
406+
_parts_list.reload_text(current_part.index);
407+
}
408+
on_select_parts(indices);
367409
event.Skip();
368410
});
369411

@@ -373,21 +415,29 @@ void main_window::bind_all_controls() {
373415
_controls.sinterbox_result_button->Bind(wxEVT_BUTTON, &main_window::on_sinterbox_result, this);
374416

375417
_controls.quantity_spinner->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent& event) {
376-
_current_part->quantity = event.GetPosition();
377-
_parts_list.reload_quantity(_current_part_index.value());
418+
for (auto& current_part : _current_parts) {
419+
current_part.part->quantity = event.GetPosition();
420+
_parts_list.reload_quantity(current_part.index);
421+
}
378422
event.Skip();
379423
});
380424
_controls.min_hole_spinner->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent& event) {
381-
_current_part->min_hole = event.GetPosition();
425+
for (auto& current_part : _current_parts) {
426+
current_part.part->min_hole = event.GetPosition();
427+
}
382428
event.Skip();
383429
});
384430
_controls.minimize_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) {
385-
_current_part->rotate_min_box = event.IsChecked();
431+
for (auto& current_part : _current_parts) {
432+
current_part.part->rotate_min_box = event.IsChecked();
433+
}
386434
event.Skip();
387435
});
388436

389437
_controls.rotation_dropdown->Bind(wxEVT_CHOICE, [this](wxCommandEvent& event) {
390-
_current_part->rotation_index = _controls.rotation_dropdown->GetSelection();
438+
for (auto& current_part : _current_parts) {
439+
current_part.part->rotation_index = _controls.rotation_dropdown->GetSelection();
440+
}
391441
event.Skip();
392442
});
393443

@@ -411,7 +461,7 @@ void main_window::on_new(wxCommandEvent& event) {
411461
{
412462
_controls.reset_values();
413463
_parts_list.delete_all();
414-
unset_part();
464+
on_select_parts({});
415465
_results_list.delete_all();
416466
unset_result();
417467
_viewport->remove_mesh();
@@ -450,7 +500,7 @@ void main_window::on_import_part(wxCommandEvent& event) {
450500
}
451501
_parts_list.update_label();
452502
if (paths.size() == 1) {
453-
const calc::part& part = *_parts_list.at(_parts_list.rows() - 1);
503+
const calc::part& part = _parts_list.at(_parts_list.rows() - 1);
454504
_viewport->set_mesh(part.mesh, part.centroid);
455505
}
456506

src/pstack/gui/main_window.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ class main_window : public wxFrame {
3030
preferences _preferences;
3131

3232
void on_select_parts(const std::vector<std::size_t>& indices);
33-
void set_part(std::size_t index);
34-
void unset_part();
3533
parts_list _parts_list{};
36-
std::shared_ptr<calc::part> _current_part = nullptr;
37-
std::optional<std::size_t> _current_part_index = std::nullopt;
34+
struct _current_part_t {
35+
calc::part* part;
36+
std::size_t index;
37+
};
38+
std::vector<_current_part_t> _current_parts{};
3839
void enable_part_settings(bool enable);
3940

4041
void on_select_results(const std::vector<std::size_t>& indices);

src/pstack/gui/parts_list.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,21 @@ calc::part make_part(std::string mesh_file, bool mirrored) {
4848
}
4949

5050
wxString quantity_string(const calc::part& part, const bool show_extra) {
51-
if (show_extra and part.base_quantity.has_value()) {
52-
const int diff = part.quantity - *part.base_quantity;
53-
if (diff > 0) {
54-
return wxString::Format("%d + %d", *part.base_quantity, diff);
55-
}
51+
if (not part.base_quantity.has_value()) {
52+
return wxString::Format("%d", part.quantity);
53+
}
54+
55+
static const wxString up_arrow = wxString::FromUTF8(" \xe2\x86\x91"); //
56+
static const wxString down_arrow = wxString::FromUTF8(" \xe2\x86\x93"); //
57+
static const wxString empty = "";
58+
59+
const int diff = part.quantity - *part.base_quantity;
60+
if (diff > 0 and show_extra) {
61+
return wxString::Format("%d + %d%s", *part.base_quantity, diff, up_arrow);
62+
} else {
63+
auto& arrow_suffix = (diff > 0) ? up_arrow : (diff < 0) ? down_arrow : empty;
64+
return wxString::Format("%d%s", part.quantity, arrow_suffix);
5665
}
57-
return wxString::Format("%d", part.quantity);
5866
}
5967

6068
} // namespace

src/pstack/gui/parts_list.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class parts_list : public list_view {
2828
void reload_quantity(std::size_t row);
2929
void delete_all();
3030
void delete_selected();
31-
std::shared_ptr<calc::part> at(std::size_t row) {
32-
return _parts.at(row);
31+
calc::part& at(std::size_t row) {
32+
return *_parts.at(row);
3333
}
3434
std::vector<std::shared_ptr<const calc::part>> get_all() const;
3535

0 commit comments

Comments
 (0)