From 56fe490ca535fa58189330e1c461a8da422566f9 Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Thu, 2 Oct 2025 17:38:03 -0500 Subject: [PATCH 1/2] Automatically load a project file on startup or 'new', if it is called '.pstack.json' in any parent directory of the current working directory --- src/pstack/gui/main_window.cpp | 72 +++++++++++++++++++++++----------- src/pstack/gui/main_window.hpp | 3 ++ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/pstack/gui/main_window.cpp b/src/pstack/gui/main_window.cpp index 1ca40c2..1b71f2a 100644 --- a/src/pstack/gui/main_window.cpp +++ b/src/pstack/gui/main_window.cpp @@ -7,6 +7,7 @@ #include "pstack/gui/viewport.hpp" #include +#include #include #include #include @@ -44,6 +45,7 @@ main_window::main_window(const wxString& title) sizer->Add(_viewport, 1, wxEXPAND); sizer->Add(arrange_all_controls(), 0, wxEXPAND | wxALL, FromDIP(constants::outer_border)); SetSizerAndFit(sizer); + try_load_environment(); } calc::stack_settings main_window::stack_settings() const { @@ -418,6 +420,51 @@ wxMenuBar* main_window::make_menu_bar() { return menu_bar; } +void main_window::try_load_environment() { + auto dir = std::filesystem::current_path(); + while (true) { + auto file = dir / ".pstack.json"; + if (std::filesystem::exists(file) and std::filesystem::is_regular_file(file)) { + if (load_project(file.string())) { + wxMessageBox(wxString::Format("Loaded environment from: %s", file.string()), "PartStacker environment", wxICON_INFORMATION); + } + break; + } + auto parent_dir = dir.parent_path(); + if (parent_dir == dir) { + break; + } + dir = std::move(parent_dir); + } +} + +bool main_window::load_project(const std::string json_path) { + auto file = files::read_file(json_path); + if (not file.has_value()) { + wxMessageBox(wxString::Format("Could not open path: %s\n\n%s", json_path, file.error()), "Error", wxICON_WARNING); + return false; + } + + auto state = save_state_from_json(*file); + if (not state.has_value()) { + wxMessageBox(wxString::Format("Could not read project file: %s\n\n%s", json_path, state.error()), "Error", wxICON_WARNING); + return false; + } + + _preferences = state->preferences; + _viewport->scroll_direction(_preferences.invert_scroll); + _parts_list.show_extra(_preferences.extra_parts); + _viewport->show_bounding_box(_preferences.show_bounding_box); + stack_settings(state->stack); + sinterbox_settings(state->sinterbox); + _parts_list.replace_all(std::move(state->parts)); + for (auto& result : state->results) { + result.reload_mesh(); + } + _results_list.replace_all(std::move(state->results)); + return true; +} + void main_window::bind_all_controls() { Bind(wxEVT_CLOSE_WINDOW, &main_window::on_close, this); @@ -510,6 +557,7 @@ void main_window::on_new(wxCommandEvent& event) { _results_list.delete_all(); unset_result(); _viewport->remove_mesh(); + try_load_environment(); } event.Skip(); } @@ -542,29 +590,7 @@ void main_window::on_open(wxCommandEvent& event) { return; } - auto file = files::read_file(dialog.GetPath().ToStdString()); - if (not file.has_value()) { - wxMessageBox(wxString::Format("Could not open path: %s\n\n%s", dialog.GetPath(), file.error()), "Error", wxICON_WARNING); - return; - } - - auto state = save_state_from_json(*file); - if (not state.has_value()) { - wxMessageBox(wxString::Format("Could not read project file: %s\n\n%s", dialog.GetPath(), state.error()), "Error", wxICON_WARNING); - return; - } - - _preferences = state->preferences; - _viewport->scroll_direction(_preferences.invert_scroll); - _parts_list.show_extra(_preferences.extra_parts); - _viewport->show_bounding_box(_preferences.show_bounding_box); - stack_settings(state->stack); - sinterbox_settings(state->sinterbox); - _parts_list.replace_all(std::move(state->parts)); - for (auto& result : state->results) { - result.reload_mesh(); - } - _results_list.replace_all(std::move(state->results)); + load_project(dialog.GetPath().ToStdString()); } event.Skip(); } diff --git a/src/pstack/gui/main_window.hpp b/src/pstack/gui/main_window.hpp index e26ddf5..780a0f1 100644 --- a/src/pstack/gui/main_window.hpp +++ b/src/pstack/gui/main_window.hpp @@ -61,6 +61,9 @@ class main_window : public wxFrame { wxMenuBar* make_menu_bar(); std::vector _disableable_menu_items; + void try_load_environment(); + bool load_project(std::string json_path); + void bind_all_controls(); void on_new(wxCommandEvent& event); void on_close(wxCloseEvent& event); From 8c953c0796a022aa440477cb20f0452264a388bb Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Thu, 2 Oct 2025 17:43:29 -0500 Subject: [PATCH 2/2] Add a preference to enable or disable the popup when loading an environment --- src/pstack/gui/main_window.cpp | 10 ++++++++-- src/pstack/gui/preferences.hpp | 1 + src/pstack/gui/save.cpp | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pstack/gui/main_window.cpp b/src/pstack/gui/main_window.cpp index 1b71f2a..8f09057 100644 --- a/src/pstack/gui/main_window.cpp +++ b/src/pstack/gui/main_window.cpp @@ -332,7 +332,7 @@ wxMenuBar* main_window::make_menu_bar() { // Menu items cannot be 0 on Mac new_ = 1, open, save, close, import, export_, - pref_scroll, pref_extra, pref_bounding_box, + pref_scroll, pref_extra, pref_bounding_box, pref_env_popup, about, website, issue, }; menu_bar->Bind(wxEVT_MENU, [this](wxCommandEvent& event) { @@ -372,6 +372,10 @@ wxMenuBar* main_window::make_menu_bar() { _viewport->show_bounding_box(_preferences.show_bounding_box); break; } + case menu_item::pref_env_popup: { + _preferences.load_environment_popup = not _preferences.load_environment_popup; + break; + } case menu_item::about: { constexpr auto str = "PartStacker Community Edition\n\n" @@ -426,7 +430,9 @@ void main_window::try_load_environment() { auto file = dir / ".pstack.json"; if (std::filesystem::exists(file) and std::filesystem::is_regular_file(file)) { if (load_project(file.string())) { - wxMessageBox(wxString::Format("Loaded environment from: %s", file.string()), "PartStacker environment", wxICON_INFORMATION); + if (_preferences.load_environment_popup) { + wxMessageBox(wxString::Format("Loaded environment from: %s", file.string()), "PartStacker environment", wxICON_INFORMATION); + } } break; } diff --git a/src/pstack/gui/preferences.hpp b/src/pstack/gui/preferences.hpp index 59f37f4..42c4db0 100644 --- a/src/pstack/gui/preferences.hpp +++ b/src/pstack/gui/preferences.hpp @@ -7,6 +7,7 @@ struct preferences { bool invert_scroll = false; bool extra_parts = false; bool show_bounding_box = false; + bool load_environment_popup = true; }; } // namespace pstack::gui diff --git a/src/pstack/gui/save.cpp b/src/pstack/gui/save.cpp index e06083b..73116fa 100644 --- a/src/pstack/gui/save.cpp +++ b/src/pstack/gui/save.cpp @@ -26,7 +26,7 @@ using internal_save_state = basic_save_state; } // namespace pstack::gui JSONCONS_N_MEMBER_TRAITS(pstack::gui::preferences, 0, // Nothing is required - invert_scroll, extra_parts, show_bounding_box + invert_scroll, extra_parts, show_bounding_box, load_environment_popup ); JSONCONS_N_MEMBER_TRAITS(pstack::calc::stack_settings, 0, // Nothing is required resolution, x_min, x_max, y_min, y_max, z_min, z_max @@ -104,7 +104,8 @@ const std::string_view the_schema = R"the_schema({ "properties": { "invert_scroll": { "type": "boolean" }, "extra_parts": { "type": "boolean" }, - "show_bounding_box": { "type": "boolean" } + "show_bounding_box": { "type": "boolean" }, + "load_environment_popup": { "type": "boolean" } } }, "stack": {